Skip to content

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.

shell
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.

download glb
curl "https://artemaze.com/api/v1/models/generate?seed=Vortex%237&format=glb" \
  -H "x-artemaze-key: amz_live_xxxxxxxxxxxxxxxxxxxx" \
  --output vortex.glb

Authentication

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

GEThttps://artemaze.com/api/v1/models/generate

Query parameters

ParameterRequiredDefaultDescription
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.
generatornosee descsuperformula (default), gyroid, or harmonic-torus.
qualitynosee descSubdivision hint, 16–80. Higher is denser and costs the same single credit. Draft ≤ 31, Studio ≤ 55, Production above.
formatnosee descjson (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:

triply periodic
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.

three.js
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:

HeaderMeaning
x-artemaze-credits-spentnosee descAlways `1` on a 200.
x-artemaze-credits-remainingnosee descTotal balance after the debit — trial plus purchased.
x-artemaze-seednosee descThe canonical tag, echoed for the `glb` format.
x-artemaze-triangle-countnosee descTriangle 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.

javascript
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`);
python
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 z
node · glb
import { 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.

generator

That seed could not be meshed. Try a different word.

No response to show.

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.

402 payment required
{
  "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 }
}
StatusCodeWhen
400missing_seedNo `seed` query parameter was supplied.
400seed_too_longThe seed exceeds 64 characters.
400unknown_generator`generator` is not one of the three ids.
400invalid_quality`quality` is not a number.
400quality_out_of_range`quality` is outside 16–80.
400invalid_format`format` is neither `json` nor `glb`.
401missing_api_keyThe `x-artemaze-key` header is absent.
401invalid_api_keyNo profile matches the key's SHA-256 hash.
402insufficient_creditsBoth the trial and purchased balances are zero.
429rate_limitedMore than 120 requests in a 60-second window for one key.
422generation_failedThe seed could not be meshed. Try a different variation.
500auth_lookup_failedThe key lookup itself failed. Retry; nothing was charged.
503service_unavailableThe 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.