Create and poll a model
Turn an uploaded file into a model and wait for inspection.
Create a model
Upload the file first, then send its file ID, a default longest_dimension_mm from 30 to 200, and optional name, external_id or metadata. Use a key with models:write and models:read from the same business as the file.
The returned ID is immutable. One file can back several models. Models remain available indefinitely unless explicitly deleted.
Create and poll
Set FILE_ID to a successfully uploaded file. The examples create a model, save its ID and poll until inspection finishes, backing off from two to ten seconds. A bounded wait stops without creating another model; resume lookup using the saved ID.
Create and poll a model
// Node.js 22+. Upload first, then set FILE_ID to the returned file ID.
// Use a key with models:write and models:read; keep it on your server.
const base = process.env.API_BASE_URL.replace(/\/$/, "");
const headers = { Authorization: `Bearer ${process.env.OTTOMFG_API_KEY}` };
// Save this key and request body before sending; reuse both on retry.
const requestKey = crypto.randomUUID();
const response = await fetch(`${base}/api/v1/models`, {
method: "POST",
headers: {
...headers,
"Content-Type": "application/json",
"Idempotency-Key": requestKey,
},
body: JSON.stringify({
source: { file_id: process.env.FILE_ID },
longest_dimension_mm: 100,
name: "First model",
}),
signal: AbortSignal.timeout(30_000),
});
if (!response.ok) {
console.error(`Create failed (${response.status}):`, await response.text());
process.exit(1); // Retry with the same key and body within 24 hours.
}
const model = await response.json();
console.log("Model ID:", model.id); // Save for later requests.
// Wait at most ten minutes; keep the ID to resume lookup if the wait expires.
const deadline = Date.now() + 600_000;
let delay = 2_000;
let finished = false;
while (Date.now() < deadline) {
const lookup = await fetch(`${base}/api/v1/models/${encodeURIComponent(model.id)}`, {
headers,
signal: AbortSignal.timeout(30_000),
});
if (!lookup.ok) {
console.error(`Lookup failed (${lookup.status}):`, await lookup.text());
process.exit(1);
}
const current = await lookup.json();
if (current.status !== "processing") {
console.log(current); // ready, or read status_reasons for action_required/failed.
finished = true;
break;
}
await new Promise((resolve) => setTimeout(resolve, delay));
delay = Math.min(delay * 1.5, 10_000);
}
if (!finished) console.log("Still processing; resume GET using model ID:", model.id);
Processing time depends on complexity and queued work. GET never restarts inspection. Stop polling when status is ready, action_required or failed. Previews can still be pending.
Recover from creation errors
| Code | Action |
|---|---|
| file_not_ready | Finish the upload, then retry. |
| file_not_found | Check the file ID, account and unlinked-file expiry. |
| invalid_argument | Correct the field identified in details. |
Save an Idempotency-Key before creation and reuse it if the response is lost. Replays return the original snapshot; GET returns current results.
API v1 preview