Developer API
Procedural 3D over HTTP
One endpoint generates the same geometry the Studio builds, with the same determinism: a seed always produces the same mesh, on the server and in your browser, byte for byte. Every successful call spends exactly one credit.
Quickstart
Paste a seed, choose a format, and the endpoint returns finished geometry. There is no session, no OAuth dance, and no request body.
curl "https://artemaze.com/api/v1/models/generate?seed=Matrix%23832&format=json" \
-H "x-artemaze-key: amz_live_xxxxxxxxxxxxxxxxxxxx"The seed is the identity of the model. Write any word — Vortex, Matrix, Titan — optionally followed by # and a three-digit variation. Without a variation, one is derived from the word, so ?seed=Matrix is still perfectly reproducible.
curl "https://artemaze.com/api/v1/models/generate?seed=Vortex%237&format=glb" \
-H "x-artemaze-key: amz_live_xxxxxxxxxxxxxxxxxxxx" \
--output vortex.glbAuthentication
Send your key in the x-artemaze-key request header. Keys are prefixed amz_live_, shown to you exactly once at creation, and stored only as a SHA-256 hash — the plaintext is never written to the database and cannot be recovered by anyone, including us. If you lose one, revoke it and create another.
Comparison is constant-time, and the key is matched against a hash rather than an index lookup on the plaintext, so neither the key nor its length is observable from response timing.
The endpoint
https://artemaze.com/api/v1/models/generateQuery parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
seed* | yes | — | The composite tag. Up to 64 characters. A trailing #nnn pins the variation; without one, a stable variation is derived from the word. |
generator | no | see desc | superformula (default), gyroid, or harmonic-torus. |
quality | no | see desc | Subdivision hint, 16–80. Higher is denser and costs the same single credit. Draft ≤ 31, Studio ≤ 55, Production above. |
format | no | see desc | json (default) for flat vertex buffers, or glb for a binary glTF download. |
Other generators
The three generators are genuinely different surfaces, not variations on one theme:
curl "https://artemaze.com/api/v1/models/generate?seed=Matrix%23832&generator=gyroid&quality=64" \
-H "x-artemaze-key: amz_live_xxxxxxxxxxxxxxxxxxxx"Responses
With format=json the body carries the geometry as four flat arrays — exactly the layout Float32Array and Uint32Array want, so no reshaping is needed on your side. Attribute order is x, y, z for positions and normals, u, v for UVs, and the index buffer is triangle-oriented.
geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(data.position), 3));
geometry.setAttribute("normal", new THREE.BufferAttribute(new Float32Array(data.normal), 3));
geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(data.uv), 2));
geometry.setIndex(new THREE.BufferAttribute(new Uint32Array(data.index), 1));Response headers
Every successful response reports the spend, whichever format you asked for:
| Header | Meaning | ||
|---|---|---|---|
x-artemaze-credits-spent | no | see desc | Always `1` on a 200. |
x-artemaze-credits-remaining | no | see desc | Total balance after the debit — trial plus purchased. |
x-artemaze-seed | no | see desc | The canonical tag, echoed for the `glb` format. |
x-artemaze-triangle-count | no | see desc | Triangle count, for the `glb` format. |
Clients
The same call from JavaScript and Python. Both surface the error body rather than swallowing it, because a 402 that is silently ignored looks exactly like a hang.
const res = await fetch(
"https://artemaze.com/api/v1/models/generate?seed=Matrix%23832&format=json",
{ headers: { "x-artemaze-key": process.env.ARTEMAZE_KEY } },
);
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${res.status} ${error.code}: ${error.message}`);
}
const { position, normal, uv, index } = await res.json();
console.log(`${position.length / 3} vertices`);import json, urllib.request
req = urllib.request.Request(
"https://artemaze.com/api/v1/models/generate?seed=Matrix%23832&format=json",
headers={"x-artemaze-key": "amz_live_..."},
)
with urllib.request.urlopen(req) as r:
body = json.load(r)
print(len(body["position"]) // 3 floats, x y zimport { writeFile } from "node:fs/promises";
const res = await fetch(
"https://artemaze.com/api/v1/models/generate?seed=Matrix%23832&format=glb",
{ headers: { "x-artemaze-key": process.env.ARTEMAZE_KEY } },
);
await writeFile("matrix.glb", Buffer.from(await res.arrayBuffer()));Live inspector
Change the seed, generator, or resolution and the response below is rebuilt with the real engine. Nothing is requested from the server and no credit is spent.
Response inspector
Runs the engine in your browser with the inputs below. Counts and parameters are the real ones — no credit is spent.
That seed could not be meshed. Try a different word.
The position, normal, uv and index arrays are truncated after their first few values; the response carries every element.
Errors
Every failure is JSON, with a stable machine-readable code and a message written for humans. Branch on the code; treat the message as display text.
{
"error": {
"code": "insufficient_credits",
"message": "Your credit balance is empty. Add credits at https://artemaze.com/pricing.",
"docs": "https://artemaze.com/docs/api#errors"
},
"credits": { "trial": 0, "purchased": 0, "total": 0 }
}| Status | Code | When |
|---|---|---|
| 400 | missing_seed | No `seed` query parameter was supplied. |
| 400 | seed_too_long | The seed exceeds 64 characters. |
| 400 | unknown_generator | `generator` is not one of the three ids. |
| 400 | invalid_quality | `quality` is not a number. |
| 400 | quality_out_of_range | `quality` is outside 16–80. |
| 400 | invalid_format | `format` is neither `json` nor `glb`. |
| 401 | missing_api_key | The `x-artemaze-key` header is absent. |
| 401 | invalid_api_key | No profile matches the key's SHA-256 hash. |
| 402 | insufficient_credits | Both the trial and purchased balances are zero. |
| 429 | rate_limited | More than 120 requests in a 60-second window for one key. |
| 422 | generation_failed | The seed could not be meshed. Try a different variation. |
| 500 | auth_lookup_failed | The key lookup itself failed. Retry; nothing was charged. |
| 503 | service_unavailable | The server has no Supabase connection, so no balance can be debited. |
No credit is spent on any non-2xx response. Geometry is meshed before the balance is touched, so a 422 or a 400 costs nothing. The one case worth designing around is a client that aborts a 200 mid-download: the credit is already spent, because the work was already done.
Credits & rate limits
One credit per successful generation, regardless of format or quality. Two pools exist and they are never merged: a 15-day trial grant, and purchased credits that never expire. Trial credits are always spent first, so expiry can never take credits you paid for.
The debit is a conditional update rather than a read-then-write, so two concurrent requests cannot both spend the same last credit — one succeeds, the other falls through to the next pool and is charged correctly.
The rate limit is 120 requests per minute per key and returns 429 with a retryAfter in seconds. It is a courtesy guard against a runaway loop; the credit balance is the actual limit.