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
- Make an account on VERTEX (free), and sign in.
- Make a key in the console. It starts with
vx_and is shown once: keep it somewhere safe, like an environment variable. - 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
| Key | For | Where it comes from |
|---|---|---|
vx_… | Engine API | You make it in the console, up to five live keys per account. |
tk_… | Tracer API | Issued 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:
| Header | What it is |
|---|---|
X-Request-Id | This call's id, like req_8KQe2f…. On every answer, errors too. Quote it when you ask for help. |
X-Vertex-Serial | The serial number stamped into the file, like VX-2610-8K2F-QP7D. On every file. |
X-Vertex-Engine | The engine version that made the file. |
X-Vertex-Parts | The 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.
| Field | Type | What it is | |
|---|---|---|---|
kind | string | required | One of the kinds. |
params | object | Settings. 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.
| Field | Type | What it is | |
|---|---|---|---|
kind | string | required | bin, baseplate, holder, labels, skadis or morph. |
params | object | Settings. Anything you leave out takes its default. | |
format | string | 3mf (default, every part, keeps colours), stl (one part) or obj (every part). | |
part | string | For STL from a model with several parts: which one. /parts lists their names. | |
name | string | The 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.txtA 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.
POST /trace/v1/jobs
Send the photo. You get a job id at once.
GET /trace/v1/jobs/:id
Check every second or two until it's done (usually a few seconds).
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.
| Query | Default | What it is |
|---|---|---|
format | stl | stl or 3mf. |
clearance | 1 | Millimetres added round each tool (0–5). |
depth | 20 | Pocket depth in millimetres (5–60). |
finger | 22 | Finger 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
| Status | Means | What to do |
|---|---|---|
| 400 | A setting the generator won't take. The message names it. | Fix that setting. /kinds shows the defaults. |
| 401 | No key, a mistyped key, or a revoked key. | Check the header and the key in the console. |
| 403 | The 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. |
| 404 | No such job (jobs last 15 minutes), or a wrong path. | Start a new job. |
| 409 | The job hasn't finished tracing. | Wait, then ask for the bin again. |
| 413 | Too many settings, or a photo over 20 MB. | Send less, or a smaller photo. |
| 423 | That generator is paused for maintenance. | Try again later. |
| 429 | Over a limit (per minute, per day, at once, or the month's photos). | Wait and retry, slowing down. The message says which limit. |
| 503 | The 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 API | Tracer API | |
|---|---|---|
| Per minute | Per key, by plan (30 on the free plan) | 6 photos per key |
| Per day | Per account, shared by all its keys, by plan (1,000 on the free plan) | — |
| Past the day's allowance | Free 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) |
| Keys | By plan (5 on the free plan) | Issued by us |
| Sizes | The 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.
| Event | Sent when |
|---|---|
usage.80 | Your account has used 80% of its calls for the day. Once a day. |
usage.limit | Your account hit its daily limit and calls are getting 429s. Once a day. |
key.created | A key was made on your account. |
key.revoked | A key was revoked, by you or by us (with our reason). |
incident.updated | An incident on the status page was opened, updated or resolved. |
webhook.test | You 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" }
}
| Header | Holds |
|---|---|
Mint-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> of <t>.<raw body>, keyed with your secret. |
Mint-Event | The event type. |
Mint-Delivery | The 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.