Skip to content
Get a key

Docs

The Mint Motive API

Two APIs on one engine. The Engine API turns settings into print-ready models: Gridfinity bins, baseplates, holders, label clips, Skådis parts and Deck Foundry parts, as STL, 3MF or OBJ. The Tracer API turns a photo of tools on a sheet of paper into outlines in millimetres, or a finished bin with a pocket for every tool.

Base URL

https://api.mintmotive.com.au

Format

JSON in, files or JSON out

Engine

—

Checking…

Every path below is relative to the base URL. The same API also answers under /api, so /engine/v1/generate is also /api/engine/v1/generate.

Quick start

  1. Make an account on VERTEX (free), and sign in.
  2. Make a key in the console. It starts with vx_ and is shown once: keep it somewhere safe, like an environment variable.
  3. Make your first model. This makes a 3 × 2 bin, six units tall, with three compartments:

The answer is the file itself. Its headers say which serial number it carries, which engine made it, which parts it holds, and the request id of the call.

Authentication

Send your key on every call, in the Authorization header:

Authorization: Bearer vx_YOUR_KEY
KeyForWhere it comes from
vx_…Engine APIYou make it in the console, up to five live keys per account.
tk_…Tracer APIIssued by us to partners, with a monthly photo allowance. Test keys work before launch.
  • Keep keys secret. Use them from a server, never in a web page or an app someone can unpack.
  • One key per use (a shop, a bot, a script), so you can see each one's calls and revoke one without stopping the rest.
  • Revoke a key in the console and it stops working at once. Its past calls stay in your log, under its name.
  • Lock a key to your server's addresses and a leaked copy is useless anywhere else. See Locking a key.
  • We only ever store a fingerprint of your key, plus its first few characters (like vx_ab12…) so you can tell keys apart.

Requests and responses

Send JSON with Content-Type: application/json, except a tracer photo, which can be the raw image. Successful calls answer with a file or with JSON. Every answer carries these headers:

HeaderWhat it is
X-Request-IdThis call's id, like req_8KQe2f…. On every answer, errors too. Quote it when you ask for help.
X-Vertex-SerialThe serial number stamped into the file, like VX-2610-8K2F-QP7D. On every file.
X-Vertex-EngineThe engine version that made the file.
X-Vertex-PartsThe parts in the file, comma separated.

An error is JSON with one plain sentence that says what went wrong and how to fix it:

{ "error": "gridX is at most 10." }

GET /engine/v1

What the engine is right now: its version, what it can make, the formats, the limits, and whether it's switched on. No key needed.

{
  "engine": "1.33.0",
  "version": 1,
  "enabled": false,
  "kinds": ["bin", "baseplate", "holder", "labels", "skadis", "morph"],
  "formats": ["stl", "3mf", "obj"],
  "limits": { "perMinute": 30, "perDay": 1000, "keys": 5 }
}

GET /engine/v1/kinds

Every kind and the default of every setting. No key needed. The settings are the same ones the generator pages use: set a model up on the site and the address bar spells out the names and values. The full list is under Kinds and settings.

POST /engine/v1/parts

A dry run. It lists the parts a model is made of, with sizes in millimetres and triangle counts, and makes no file. It's handy for checking settings, or for asking for one part as STL.

FieldTypeWhat it is
kindstringrequiredOne of the kinds.
paramsobjectSettings. Anything you leave out takes its default.
{
  "kind": "bin",
  "engine": "1.33.0",
  "parts": [
    { "name": "bin", "triangles": 2688, "size": [125.5, 83.5, 46.6] }
  ]
}

POST /engine/v1/generate

Makes the model and answers with the file.

FieldTypeWhat it is
kindstringrequiredbin, baseplate, holder, labels, skadis or morph.
paramsobjectSettings. Anything you leave out takes its default.
formatstring3mf (default, every part, keeps colours), stl (one part) or obj (every part).
partstringFor STL from a model with several parts: which one. /parts lists their names.
namestringThe file's name, without the extension.
curl -X POST https://api.mintmotive.com.au/engine/v1/generate \
  -H "Authorization: Bearer $MINT_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kind": "baseplate", "format": "3mf", "params": { "gridX": 6, "gridY": 4, "bed": 256 } }' \
  -o baseplate.3mf -D headers.txt

A baseplate bigger than your bed comes back split into tiles that fit, with the clips that join them, all in one 3MF.

Kinds and settings

Straight from the engine, so this list is always current. Pick a kind:

Loading the engine's settings…

How tracing works By invitation

Lay tools on a sheet of paper and photograph them from above. The paper is the ruler: we find its corners, straighten the photo, and measure every tool in millimetres.

1

POST /trace/v1/jobs
Send the photo. You get a job id at once.

2

GET /trace/v1/jobs/:id
Check every second or two until it's done (usually a few seconds).

3

Use the outlines, or GET …/bin for a ready bin with a pocket per tool.

Photos are traced and thrown away; jobs are kept for 15 minutes. A photo that fails costs nothing from your allowance.

GET /trace/v1

Your key's name, whether it's a test key, and this month's allowance and use.

{
  "name": "VERTEX tracer API",
  "version": 1,
  "papers": ["a4", "letter", "a5", "a3"],
  "key": { "name": "Acme Tools", "test": false, "quota": 500, "usedThisMonth": 42 },
  "limits": { "perMinute": 6, "atOnce": 2, "maxPhotoMb": 20 }
}

POST /trace/v1/jobs

Send the photo as the request body (JPEG, PNG or HEIC, up to 20 MB), or as JSON { "image": "<base64>", "paper": "a4" }. Say the paper size with ?paper=: a4 (default), letter, a5 or a3.

curl -X POST "https://api.mintmotive.com.au/trace/v1/jobs?paper=a4" \
  -H "Authorization: Bearer $TRACE_KEY" \
  -H "Content-Type: image/jpeg" \
  --data-binary @tools.jpg
{ "id": "Qm9zc2Vz8a", "status": "running", "check": "/api/trace/v1/jobs/Qm9zc2Vz8a" }

GET /trace/v1/jobs/:id

While it runs: { "status": "running", "seconds": 2 }. If it fails: { "status": "failed", "error": "…" } with what to change. When it's done:

{
  "id": "Qm9zc2Vz8a",
  "status": "done",
  "paper": { "name": "A4", "widthMm": 210, "heightMm": 297 },
  "tools": [
    {
      "name": "pliers",
      "lengthMm": 215.6,
      "widthMm": 58.2,
      "areaMm2": 6120.4,
      "outline": [[12.4, 30.1], [14.0, 29.6], "…"]
    }
  ],
  "bin": "/api/trace/v1/jobs/Qm9zc2Vz8a/bin"
}

Outlines are in millimetres on the straightened sheet, laid out as the tools lay on the paper. name is what the tool looks like, or just tool.

GET /trace/v1/jobs/:id/bin

A Gridfinity bin with a pocket for every tool: the smallest bin they fit in, up to 8 × 8. It carries a serial like any file.

QueryDefaultWhat it is
formatstlstl or 3mf.
clearance1Millimetres added round each tool (0–5).
depth20Pocket depth in millimetres (5–60).
finger22Finger hole diameter (0 for none, up to 35).

Taking good photos

  • Straight from above, with the whole sheet in view and a little table round it.
  • A darker surface than the paper, so its edges stand out.
  • Even light: daylight or two lamps. Avoid a single hard light that throws long shadows.
  • Tools flat and apart, not touching each other or the edge of the sheet.
  • Shiny steel: tilt the light a little to kill glare.

Errors

StatusMeansWhat to do
400A setting the generator won't take. The message names it.Fix that setting. /kinds shows the defaults.
401No key, a mistyped key, or a revoked key.Check the header and the key in the console.
403The key is locked to other addresses, a tracer key was revoked, or calls from your address are blocked.Call from an allowed address, or update the key's lock. Write to us if you think a block is wrong.
404No such job (jobs last 15 minutes), or a wrong path.Start a new job.
409The job hasn't finished tracing.Wait, then ask for the bin again.
413Too many settings, or a photo over 20 MB.Send less, or a smaller photo.
423That generator is paused for maintenance.Try again later.
429Over a limit (per minute, per day, at once, or the month's photos).Wait and retry, slowing down. The message says which limit.
503The API isn't switched on yet.Watch the status on the overview page.

Retry 429 and 5xx answers with a growing wait (1 s, 2 s, 4 s…). Don't retry 4xx answers unchanged: they'll fail the same way.

Limits, plans and quotas

Your account's plan sets the engine API's limits. The day's allowance belongs to your account and is shared by all your keys: more keys never means more calls. The per-minute limit is per key, so one busy script can't use it all up. The plans and prices are on the overview, and you can change plan in the console at any time.

Engine APITracer API
Per minutePer key, by plan (30 on the free plan)6 photos per key
Per dayPer account, shared by all its keys, by plan (1,000 on the free plan)—
Past the day's allowanceFree plan: 429 until tomorrow. Paid plans: keeps working, priced per 1,000 calls and billed daily, up to your monthly limit—
At once—2 photos per key
Per month—Your allowance (500 to start)
KeysBy plan (5 on the free plan)Issued by us
SizesThe same limits as the site's generators (grid size, height, parts).

Every file comes back with X-Mint-Plan (your plan's id). If the call counted as extra use past the day's allowance, it also has X-Mint-Extra-Use: 1. Set a monthly limit for extra use in the console. Once it's reached, calls get a 429 saying so until the 1st.

Need more? Tell us what you're building.

Locking a key to your servers

In the console, press Lock on a key and list the addresses it may be used from: single IPs (IPv4 or IPv6), or IPv4 ranges like 203.0.113.0/24, up to 20. From anywhere else the key answers 403, even with the right key. Leave the list empty to let the key work from anywhere again.

Lock keys that run on servers with fixed addresses. Don't lock a key used from a laptop or a home connection whose address changes.

Webhooks

Add an https:// address in the console and we'll POST to it when something happens to your API account. You get a signing secret (whsec_…) once, when you add it.

EventSent when
usage.80Your account has used 80% of its calls for the day. Once a day.
usage.limitYour account hit its daily limit and calls are getting 429s. Once a day.
key.createdA key was made on your account.
key.revokedA key was revoked, by you or by us (with our reason).
incident.updatedAn incident on the status page was opened, updated or resolved.
webhook.testYou pressed Send a test.

Each delivery is JSON, with three headers:

{
  "id": "evt_8sKq2mZ0aB1c",
  "type": "usage.80",
  "created": 1790000000,
  "data": { "key": { "id": 12, "name": "Shop orders", "hint": "vx_ab12…" }, "used": 800, "limit": 1000, "window": "24h" }
}
HeaderHolds
Mint-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256> of <t>.<raw body>, keyed with your secret.
Mint-EventThe event type.
Mint-DeliveryThe event id. The same id comes again on a retry, so you can skip repeats.

Check every signature before trusting a delivery, and refuse ones more than five minutes old:

import crypto from "node:crypto";

// rawBody: the request body exactly as it arrived, before JSON.parse.
function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const got = Buffer.from(parts.v1 || ""), want = Buffer.from(expected);
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return fresh && got.length === want.length && crypto.timingSafeEqual(got, want);
}
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return abs(time.time() - int(parts["t"])) < 300 and hmac.compare_digest(expected, parts.get("v1", ""))
  • Answer 2xx quickly (within 8 seconds), then do the work. Anything else counts as a failure.
  • Retries: after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then the delivery is marked failed. Every try is listed under Deliveries in the console, with the answer we got.
  • We don't follow redirects, and addresses must be public: private and local networks are refused.
  • Up to five webhooks per account.

Request ids and serials

Everything you make can be traced, by you and by us:

  • Every call gets a request id (X-Request-Id), errors included.
  • Every file carries a serial number (X-Vertex-Serial), stamped into the file. It records when and how the file was made, and nothing about you.
  • Your console lists every call your keys made: when, the endpoint, what you asked for, the result, how long it took, the serial and the request id. Search it by request id, serial or path.
  • We watch for trouble around the clock: sudden spikes, keys that keep failing, key guessing and scraping. If something looks wrong with your key we may get in touch, or block an address while we look.
  • Delete your VERTEX account and the account, IP address, client and settings on your calls are wiped from the log; only anonymous counts stay.
  • We keep the call log for two years, to answer support questions, spot abuse and keep the service healthy. It holds the key used, the account, the IP address and the client's user agent, never the key itself.

Versions and changes

The API is versioned in its path (/v1). Within v1 we only add things: new kinds, new settings, new fields. Nothing you rely on is removed or renamed without a new version and notice.

The engine has its own version, sent with every file. When a generator's shapes change, the engine version moves on, and old files can always be matched to the engine that made them. Recent engine changes:

Loading…

Licence and fair use

  • Models you generate are CC BY-NC-SA 4.0: free to print and remix with credit, not for sale. To sell what you make, apply for a commercial licence.
  • Generate what someone asked for, when they asked. Please don't loop the API to build a catalogue.
  • Don't share keys, or use one key for many unrelated customers.
  • We may slow or revoke a key that's hurting the service, and we'll tell you why.

Support

Stuck? Write to us, or ask in the Discord. Include the request id (or the file's serial), and we can see exactly what happened.

Is something down? The status page shows every part of the API, 90 days of history and any incident we are working on. Follow it by RSS, or with an incident.updated webhook.