---
title: "Export Geometry"
method: POST
path: "/v1/geometry/export"
tags: ["v1", "geometry"]
---

# Export Geometry

`POST /v1/geometry/export`

## Request

- Base URL: `https://thrustlab.com`
- URL: `https://thrustlab.com/v1/geometry/export`
- Auth: HTTP bearer

Export the caller's OWN working/saved geometry to STEP / IGES / STL bytes.

GATED: CAD export requires the ``cad_export`` capability
(Pro) AND exports OWN-CUSTOM geometry only. The capability gate runs
FIRST (before any geometry is resolved) so a non-Pro user gets a legible 402
``upgrade_required`` rather than a 404. A CATALOG (public) ``component_id`` is
NEVER the export source — only the caller's OWN custom prop or their inline
working geometry. The curated propeller geometry moat thus reaches CAD
bytes, not just component specs.

SYNC binary download: lofts the geometry into a watertight N-blade solid in
an isolated one-shot subprocess and returns the bytes with a
``Content-Disposition: attachment`` header + the CAD MIME per format. NO
``response_model`` (raw bytes; mirrors export.csv).

Two input paths (exactly one, enforced by the serializer):
* inline ``section_coords`` + chord/twist/radius — the primary path; the editor
 already holds these from ``/v1/geometry/generate`` (the user's OWN design, no
 DB read — allowed for a cad_export-capable user).
* ``component_id`` — re-derives the section_coords from the saved ``spec_json``
 via the deterministic generator, resolved to the caller's OWN CUSTOM
 prop only (a catalog/public id -> the same 404 as a foreign-private id;, anti-enumeration).

A loft that cannot close to a valid solid -> 422 ``geometry_not_loftable``
(CadExportError, never a 500). The filename is sanitized.

## Request body

- ExportGeometryRequest: Export the editor's working geometry (or a saved prop) to CAD bytes. Exactly ONE of (``section_coords`` inline | ``component_id`` saved-prop) is required (mutually exclusive — the ``@model_validator``). The inline path is the primary one: the editor always holds ``section_coords`` from ``/v1/geometry/generate``, so the route skips the DB entirely. The ``component_id`` path re-runs the deterministic geometry generator from the saved ``spec_json`` (caller-scoped, IDOR-safe at the route). Strictness (the geometry house style): * ``extra="forbid"`` — a stray field (e.g. a motor_id) is rejected. * every numeric field carries a bound; the geometry arrays are capped at 200 stations and ``num_blades`` at ``MAX_NUM_BLADES`` (16) — the DoS guard, enforced BEFORE any loft. * when ``section_coords`` is present, every station must share the same point count (a well-posed loft requires it) — the ``@model_validator``.
  - `chord` number[], nullable
  - `component_id` string, nullable
  - `diameter` number, nullable: Diameter in inches
  - `format` 'step' | 'iges' | 'stl'
  - `hub_radius` number, nullable
  - `include_hub` boolean, nullable: Deprecated no-op: the hub and hub-to-root transition are always included in the exported body.
  - `name` string, nullable
  - `num_blades` integer
  - `radius` number[], nullable: Absolute radii in meters
  - `rotation` 'cw' | 'ccw', nullable
  - `section_coords` unknown[], nullable
    - unknown
  - `sweep` number[], nullable
  - `twist` number[], nullable: Twist in degrees

## Response `200`

Successful Response

- unknown

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/thrustlab/apis/thrustlab-api.md) · [All operations](https://skmtc.dev/thrustlab/apis/thrustlab-api/llms.txt) · [OpenAPI document](https://skmtc.dev/thrustlab/apis/thrustlab-api/revisions/c19717e52529?raw)
