API v1 preview

# 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 a model reference](https://ottomfg.com/docs/create-model.md)

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

[Read inspection results](https://ottomfg.com/docs/inspection.md)

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

[Idempotent requests](https://ottomfg.com/docs/idempotency.md)

API v1 preview

Source: https://ottomfg.com/docs/models
