Building blocks
Uploading files
Two ways to get CAD and drawings into ARCNM: one-shot upload-and-quote, or a presigned direct upload you can reuse across calculations. STEP, PDF, PNG, JPEG.
Every quote starts from a file. ARCNM accepts STEP 3D geometry
(.step/.stp), the DXF flat pattern of a sheet part (.dxf, see
below), 2D drawings as PDF, PNG or JPEG, optional STL/OBJ meshes, and
supporting documents, up to 512 MB each (CAD is capped lower — see
below). There are two ways in.
STEP is the only 3D format accepted — it is the one that carries B-rep geometry. IGES, Parasolid, SolidWorks and other native kernel formats are not accepted: a presign or upload declaring them is rejected rather than accepted and dropped later.
DXF flat patterns
A sheet part that has no 3D model — or whose model does not unfold —
arrives as the flat pattern its cutter reads. Upload the .dxf in the
CAD slot (cad_file on /upload-and-quote, role cad_3d on a revision)
together with two things a 2D file cannot carry: sheet_gauge_mm, the
sheet thickness, and a material (material_grade_id or
material_ref). Both are required for a DXF (422 dxf_gauge_required /
dxf_material_required). The part is then priced as a flat sheet part cut
to that pattern — nested on your stocked formats, the contours cut, the
skeleton scrap — with one low-severity note that no 3D model was read (so
no bends or forms are known).
What the import reads: every geometric entity of the model space, blocks
exploded — polylines (bulges honoured), lines, arcs, circles, ellipses and
splines, curves flattened at 0.2 mm. Units come from $INSUNITS:
millimetres (4) or inches (1); a file with any other unit, unitless files
included, is refused (422 dxf_units_unsupported). One closed outer
contour per file; every other closed loop inside it is an opening. A file
with two outer contours, an island inside an opening or crossing contours
(dxf_multiple_outer_contours), a chain of lines and arcs that does not
close (dxf_open_loop, the loose end named in details), no closed
contour at all (dxf_no_contour) or one that cannot be read
(dxf_unreadable) is refused at upload, before anything is stored.
curl -X POST https://api.arcnm.io/api/v1/calculations/upload-and-quote \
-H "X-API-Key: $ARCNM_API_KEY" \
-F part_number=BRK-17 -F sheet_gauge_mm=2 -F material_ref=1.4301 \
-F "[email protected];type=application/dxf"
One-shot: upload and quote
The fastest path — hand the bytes and the costing inputs to
POST /api/v1/calculations/upload-and-quote in a single multipart
call, and ARCNM stores the file, creates the part + revision, and
enqueues the calculation. This is the quickstart path;
use it whenever you're costing a file once. To price the upload in
several environments at once, repeat the costing_environment_ids
field (up to 16): the file is still uploaded and stored once, and
one calculation per environment is enqueued under a shared batch_id.
Reusable: presigned direct upload
When you want to attach a file to several calculations, push large files
straight to storage, or separate ingest from costing, use the
presign → send → confirm flow on the /uploads resource
(uploads:write):
# 1. Ask where and how to send the file.
curl -X POST https://api.arcnm.io/api/v1/uploads/presign \
-H "X-API-Key: $ARCNM_API_KEY" -H "Content-Type: application/json" \
-d '{ "name": "bracket.step", "content_type": "application/step", "size_bytes": 184320 }'
# → { "data_source_id": "ds_…", "url": "https://…", "method": "PUT" or "POST",
# "headers": { … } or null, "fields": { … } or null, "expires_in": 900 }
# 2. Send the bytes straight to storage — the payload never touches the API.
# `method` says how; a file sent the other way is refused and never arrives.
# "PUT": the raw bytes as the body, with exactly the `headers` named.
curl -X PUT "<url>" -H "Content-Type: application/step" --data-binary @bracket.step
# "POST": a multipart form — every entry of `fields` exactly as answered
# (one --form-string each: it never reads a leading `@` or `<` as a file),
# then the file as the last field, named `file`.
curl -X POST "<url>" --form-string "key=$KEY" --form-string "policy=$POLICY" \
--form-string "…=…" -F "[email protected]"
# 3. Confirm — ARCNM checks the file (magic bytes, declared size) and keeps it.
curl -X POST https://api.arcnm.io/api/v1/uploads/confirm \
-H "X-API-Key: $ARCNM_API_KEY" -H "Content-Type: application/json" \
-d '{ "data_source_id": "ds_…" }'
# → { "data_source_id": "ds_…", "status": "pending" }
The check runs after confirm answers: status is pending until it is
done, confirmed after. Confirm is safe to repeat and answers the current
status; listing your uploads shows it too. When the upload cannot be
confirmed, confirm answers 409 and says why in details.reason:
details.reason |
What to do |
|---|---|
upload_not_received |
No file has arrived yet. Send it as method says, then confirm again |
upload_rejected |
The file was refused when it was checked: its content does not match its declared type, it is larger than declared, or it would exceed your plan's storage. Upload it again |
upload_failed |
The file could not be checked. Retry it with POST /api/v1/uploads/{id}/retry |
upload_cancelled |
The upload was cancelled. Retry it with POST /api/v1/uploads/{id}/retry to have it checked |
The data_source_id is durable: attach it to a revision
(POST /api/v1/parts/{part_id}/revisions/{revision_id}/datasets) and
quote that revision as many times — and in as many environments — as
you like without re-uploading a byte. Attached while it is still
pending, the upload answers 409 with details.reason upload_pending,
a Retry-After header and the same wait as details.retry_after_s: ask
again then. upload_rejected, upload_failed and upload_cancelled do not
change by waiting.
Storage. Until it is confirmed, an upload reserves its declared
size_bytes of your plan's storage — the most its file type allows when it
declares none, so declare the size. Confirm checks the plan again on the
size that arrived (402 entitlement_exceeded). If url expires with
nothing sent, the reservation is released; a file sent through url after
its upload was deleted or refused is removed once url expires.
Why presign? The file bytes go directly to object storage, not through the API, so a 512 MB STEP never touches the request path — faster uploads, and no JSON body-size ceiling to fight.
Allowed types
| Kind | Content types |
|---|---|
3D CAD (role cad_3d) |
application/step, model/step+xml. Validated against the ISO-10303-21 header — a non-STEP payload returns 422 cad_file_not_step |
DXF flat pattern (role cad_3d) |
application/dxf, image/vnd.dxf. Judged as a DXF (header, units, one closed outer contour) and refused with a dxf_* code otherwise; needs sheet_gauge_mm and a material — see above |
Mesh (role mesh) |
model/stl, application/sla. No B-rep: envelope, mass and surface area only |
2D drawings (role drawing_2d) |
application/pdf, image/png, image/jpeg, image/webp |
| Documents | application/pdf, text/csv, text/plain, Office formats |
content_type is required at presign and validated again by magic-byte
sniffing on confirm. Executable / scriptable types (HTML, JavaScript,
SVG) are rejected outright.
Manage uploads
| Operation | Endpoint |
|---|---|
| List your uploads | GET /api/v1/uploads |
| Get a download URL | GET /api/v1/uploads/{id}/download-url |
| Retry a failed ingest | POST /api/v1/uploads/{id}/retry |
| Cancel a pending upload | POST /api/v1/uploads/{id}/cancel |
| Bulk retry / cancel | POST /api/v1/uploads/bulk-retry, .../bulk-cancel |
See also
- Quickstart — upload-and-quote in five minutes.
- API → Uploads — presign, confirm, and the lifecycle routes.
- API → Parts — attach a data source to a part revision.