Skip to content

Latest commit

 

History

History
202 lines (147 loc) · 8.05 KB

File metadata and controls

202 lines (147 loc) · 8.05 KB

Generate 3D Models from Code

Everything the Forge does in the browser is available as a plain HTTP API: send a prompt, poll a job, download a GLB. No API key, no SDK, no auth handshake — it's the same public endpoint the Forge UI calls, rate-limited per IP.

What you'll build: a script that turns any text prompt into a downloaded .glb file, then a batch version that generates a whole asset pack from a prompt list.

Prerequisites: comfortable with fetch or curl. Examples use Node.js 18+ (built-in fetch), but any language with an HTTP client works the same way.


The flow in one picture

POST /api/forge          { prompt, tier }     →  { job_id, status: "queued" }
GET  /api/forge?job=ID   (poll every few s)   →  { status: "running", step: "mesh" }
GET  /api/forge?job=ID                        →  { status: "done", glb_url: "https://..." }
GET  glb_url                                  →  your model

One wrinkle worth knowing up front: on the fast free lane the first response can already be status: "done" with a glb_url — no polling needed. Handle both cases and you're done.


Step 1 — Generate from a prompt

curl -s https://three.ws/api/forge \
  -H 'content-type: application/json' \
  -d '{"prompt": "a glazed ceramic teapot", "tier": "draft"}'

Response (async case):

{
  "job_id": "",
  "creation_id": "",
  "status": "queued",
  "mode": "text_to_3d",
  "tier": "draft",
  "backend": "",
  "eta_seconds": 13
}

The request body:

Field Type Notes
prompt string The object description, 3–1000 chars. Same prompt rules as the UI.
tier "draft" | "standard" | "high" Polygon budget + textures. Defaults to standard. High adds PBR materials.
aspect_ratio "1:1" | "4:3" | "3:4" | "16:9" | "9:16" Shape of the intermediate reference image. Optional.
image_urls string[] 1–4 public HTTPS image URLs for photo→3D (Step 4). Omit for text→3D.
backend string Pin a specific engine from the catalog (Step 5). Optional — the Forge picks for you.

Step 2 — Poll until done

curl -s 'https://three.ws/api/forge?job=JOB_ID'

While running you'll see progress:

{ "job_id": "", "status": "running", "step": "mesh" }

step walks through image (painting the reference) → mesh (reconstruction) → finish. When it completes:

{ "job_id": "", "status": "done", "glb_url": "https://three.ws/cdn/…" }

If status is "failed", the response carries an error message — usually a prompt or quota problem you can act on.


Step 3 — The whole thing as one script

// generate.js — node generate.js "a glazed ceramic teapot"
import { writeFile } from 'node:fs/promises';

const BASE = 'https://three.ws';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function generateModel(prompt, tier = 'standard') {
  const submit = await fetch(`${BASE}/api/forge`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ prompt, tier }),
  });
  let job = await submit.json();
  if (!submit.ok) throw new Error(job.message || job.error);

  // Fast lane: the model may already be done in the first response.
  while (job.status !== 'done') {
    if (job.status === 'failed') throw new Error(job.error);
    await sleep(4000);
    const poll = await fetch(`${BASE}/api/forge?job=${encodeURIComponent(job.job_id)}`);
    job = await poll.json();
    console.log(`  ${job.status}${job.step ? ` (${job.step})` : ''}`);
  }
  return job.glb_url;
}

const prompt = process.argv[2] || 'a glazed ceramic teapot';
console.log(`Generating: ${prompt}`);
const glbUrl = await generateModel(prompt, 'draft');
const glb = await fetch(glbUrl);
const file = `${prompt.replace(/\W+/g, '-').slice(0, 40)}.glb`;
await writeFile(file, Buffer.from(await glb.arrayBuffer()));
console.log(`Saved ${file}`);

Run it:

node generate.js "a low-poly treasure chest, iron-banded wood"

Batch an asset pack by looping prompts through generateModel one at a time. Keep it sequential — the endpoint is rate-limited per IP, so a Promise.all over twenty prompts will hit 429 rate_limited. On a 429, wait and retry; it's a per-minute window, not a ban.


Step 4 — Photo → 3D from code

Two requests: get a presigned upload slot, PUT your image to it, then pass the returned public URL into the generate call.

async function uploadImage(buffer, contentType = 'image/jpeg') {
  const presign = await fetch(`${BASE}/api/forge-upload`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ content_type: contentType, size_bytes: buffer.byteLength }),
  });
  const slot = await presign.json();
  await fetch(slot.upload_url, { method: slot.method, headers: slot.headers, body: buffer });
  return slot.public_url;
}

// Then:
const url = await uploadImage(await readFile('front.jpg'));
// 1–4 views of the same object; more views = better reconstruction
const submit = await fetch(`${BASE}/api/forge`, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ image_urls: [url], tier: 'standard' }),
});

Photos are pre-checked by a vision model before any generation is spent; an unusable image comes back as a 400 explaining the problem (blurry, busy background, multiple objects). Pass skip_validation: true to override — the same as the UI's "Generate anyway" button. The photo guidelines apply exactly as in the browser.


Step 5 — Discover engines and pricing

The catalog endpoint is the live source of truth for what's available right now:

curl -s 'https://three.ws/api/forge?catalog'

It returns every backend (id, label, which paths it serves, whether it needs a bring-your-own key, ETA) and every tier with its polygon budget and price. Engines that need your own Meshy or Tripo key accept it per-request via the x-forge-provider-key header — it's used for that call and never stored.


Step 6 — For autonomous agents: pay per call with x402

The public endpoint is rate-limited per IP, which is fine for scripts and prototyping. Agents that need guaranteed, metered capacity can use the paid twin at POST /api/x402/forge — same request body, billed per generation in USDC over the x402 protocol (Base or Solana):

Tier Price per generation
Draft $0.05
Standard $0.15
High $0.50

The flow is standard x402: the first call returns a 402 with payment instructions, your x402 client settles it, and the retried call returns { job_id, poll_url } — then you poll exactly as in Step 2. Retries with the same payment are idempotent, so a network hiccup never double-charges. A bare GET /api/x402/forge returns the current price list.

If you've never made an x402 call, start with Build a paid x402 endpoint, which covers the client side too.


Troubleshooting

Response Meaning Fix
429 rate_limited Per-IP window exhausted Back off and retry; keep batches sequential
400 with image feedback Photo failed the vision pre-check Reshoot per the message, or send skip_validation: true
status: "failed" mid-job Upstream generation error Read error; retry once — transient provider errors happen
glb_url 404s later You waited a long time to download Download promptly after done; re-fetch the job for a fresh URL

What's next