---
title: "Analyze Geometry"
method: POST
path: "/v1/geometry/analyze"
tags: ["v1", "geometry"]
---

# Analyze Geometry

`POST /v1/geometry/analyze`

## Request

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

Aero-only operating-point analysis (point / sweep / solve-to-target).

Wraps the analyze service on the editor's live working geometry.
AERO-ONLY — there is NO motor/battery/ESC field anywhere; the strict
``extra="forbid"`` serializer is the guard. SYNCHRONOUS on the warm JIT
cache — a plain 200 with the result, never a 202/job id. STATELESS:
the editor's inline arrays carry everything, so this touches NO DB and reads
no current user (no saved-component path -> no IDOR surface).

A genuine build/solve failure (degenerate geometry, bad target) rewraps
``ValueError -> 422`` with a user-safe message and no traceback (mirrors the process_image idiom). A NON-converged solve-to-target is NOT an
error: it returns a normal 200 with ``converged: false`` + a ``reason``.

## Request body

- AnalyzeGeometryRequest: Aero-only operating-point analysis request (point / sweep / solve-to-target). Mirrors ``CalibrateRequest``'s geometry block + ``extra="forbid"`` so an unknown field (e.g. a stray ``motor_id``) is rejected at the boundary — the schema itself is the aero-only guard. Every scalar carries numeric bounds and the sweep ``j_steps`` is capped at 200.
  - `air_density` number: Air density kg/m^3
  - `airfoil` string
  - `airspeed` number: Freestream m/s
  - `chord` number[], required
  - `diameter` number, required: Diameter in inches
  - `hub_radius` number, nullable
  - `j_from` number, nullable
  - `j_steps` integer, nullable: Sweep point count (DoS bound — each step is a full solve)
  - `j_to` number, nullable
  - `mode` 'point' | 'sweep' | 'solve_to_target'
  - `num_blades` integer
  - `radius` number[], required
  - `rpm` number
  - `section_airfoils` unknown[]: Per-station Kulfan CST sections: {lower_weights, upper_weights, leading_edge_weight, TE_thickness} per station. The legacy [lower_w, upper_w] pair form is still accepted on read.
    - unknown
  - `sweep` number[], nullable
  - `target_type` 'thrust' | 'power', nullable
  - `target_value` number, nullable
  - `thickness` number[], nullable
  - `twist` number[], required: Twist in degrees

## Response `200`

Successful Response

- AnalyzeGeometryResponse: Bounded aero-only union response for all three modes. Every field is optional so a single shape carries a point, a sweep, OR a non-converged solve (``converged: false`` + ``reason``). NO motor/battery field anywhere.
  - `Cp` number, nullable
  - `Cp_raw` number, nullable: Power coefficient from the raw blade-element solve, before the empirical correction.
  - `Ct` number, nullable
  - `Ct_raw` number, nullable: Thrust coefficient from the raw blade-element solve, before the empirical correction. The `stations` table integrates to this.
  - `J` number, nullable
  - `converged` boolean, nullable
  - `efficiency` number, nullable
  - `envelope_limiting_factor` 'advance_ratio' | 'tip_mach' | 'pitch_diameter' | 'thrust_coefficient' | 'diameter' | 'blade_count', nullable: Which dimension takes this solve outside the validation envelope. Absent when `envelope_status` is `validated` or absent.
  - `envelope_status` 'validated' | 'partial' | 'outside', nullable: Where this solve sits relative to the published validation envelope (17 two-bladed APC Thin-Electric propellers, in diameter, advance ratio J <= 0.95, tip Mach <= 0.43). `validated` = operating point and propeller both inside it. `partial` = outside in one dimension but still carrying most of the validated model — the common case, and not an error. `outside` = far enough out that the answer is the underlying physics model's own prediction. Absent when the envelope could not be established for this solve.
  - `mode` string, required
  - `object` string
  - `power` number, nullable
  - `power_raw` number, nullable: Shaft power in W from the raw blade-element solve.
  - `reason` string, nullable
  - `rpm` number, nullable
  - `stations` AnalyzeStationRow[]
    - `Cd` number, required
    - `Cl` number, required
    - `Mach` number, required
    - `Re` number, required
    - `beta_deg` number, required
    - `chord` number, required
    - `dQ_dr` number, required
    - `dT_dr` number, required
    - `eta_local` number, required
    - `r_over_R` number, required
    - `va` number, required
    - `vt` number, required
  - `sweep_Cp` number[]
  - `sweep_Ct` number[]
  - `sweep_J` number[]
  - `sweep_eta` number[]
  - `thrust` number, nullable
  - `thrust_raw` number, nullable: Thrust in N from the raw blade-element solve. sum(dT_dr * dr) over `stations` equals THIS, not `thrust`.
  - `torque_raw` number, nullable: Torque in N·m from the raw blade-element solve. sum(dQ_dr * dr) over `stations` equals THIS.

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