krad — 3D CAD API ================= > krad is a browser-based 3D CAD tool with a programmatic REST + WebSocket API. > Any authenticated client can create 3D geometry, manipulate it, and export > STL/GLB/OBJ/PLY files. ## Authentication The recommended path for MCP clients and agents is **OAuth 2.0 / OIDC**. This server publishes discovery metadata so a compliant client can self-configure without any hand-coded URLs: - `https://staging.krad.app/.well-known/openid-configuration` — OpenID Connect Discovery 1.0 - `https://staging.krad.app/.well-known/oauth-authorization-server` — RFC 8414 OAuth AS metadata - `https://staging.krad.app/.well-known/oauth-protected-resource` — RFC 8707 protected-resource metadata - `https://staging.krad.app/.well-known/mcp/server-card.json` — MCP server card (SEP-1649) Supported grants: `authorization_code` (with PKCE — `S256` or `plain`) and `refresh_token`. Scopes: `openid profile email phone`. After exchanging the authorization code for an `access_token`, send it on every request as: ``` Authorization: Bearer ``` For server-to-server scripts that don't run an OAuth flow, you can create a long-lived **personal access token** at `https://staging.krad.app/user#api` (Settings → API tab). Tokens are `krad_` strings and are sent the same way: ``` Authorization: Bearer krad_ ``` Both token shapes route through the same authenticator — pick whichever fits the client. ## MCP This server speaks MCP at `https://staging.krad.app/.well-known/mcp/server-card.json`. Every SceneCommand below is exposed as a tool over MCP, plus a `batch` meta-tool that runs N tool calls in one request. Prefer MCP when your client supports it — the round-trip and ID-substitution machinery (`"$N"` references the result of operation N in a batch) is already wired up. The JSON-RPC endpoint is `POST https://staging.krad.app/mcp` (note: `/mcp`, not `/api/mcp`). The discovery URL above advertises OAuth, but **OAuth is only required for clients that can't hold a static secret** (e.g. Claude Chat). For local development and server-to-server scripts you do NOT need the OAuth flow — send your personal `krad_` token as a Bearer directly on `/mcp`, exactly like the REST endpoints: ```bash # tools/list — no OAuth, just the krad_ token: curl -X POST https://staging.krad.app/mcp \ -H "Authorization: Bearer krad_" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' # tools/call — e.g. render a snapshot of an existing project. The image # comes back as an `image` content block (base64 PNG); the REST # /command path can't return it, so use MCP for snapshot: curl -X POST https://staging.krad.app/mcp \ -H "Authorization: Bearer krad_" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call", "params":{"name":"snapshot", "arguments":{"project_id":"PROJECT_ID", "view":"three-quarter","size":1024}}}' ``` A missing/invalid token returns HTTP 401 with a hint pointing at `/user#api`; a GET (or wrong path like `/api/mcp`) returns 405. Both token shapes (OAuth `access_token` and personal `krad_`) route through the same authenticator, so the `krad_` shortcut is a first-class path, not a backdoor. ## Quick Start ```bash # If the user gives you a project ID, use it directly — DON'T call create! # Just send commands to /api/scene/PROJECT_ID/command. # Only call create when you need a brand new blank project: curl -X POST https://staging.krad.app/api/scene/create \ -H "Authorization: Bearer YOUR_TOKEN" # Returns: {"project_id": "..."} # Add shapes and manipulate them (use existing project ID or one from create). # Dimensions are in the session's active unit (default: millimeters). # Call set_unit once to change it; subsequent commands use the new unit. curl -X POST https://staging.krad.app/api/scene/PROJECT_ID/command \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"commands": [ {"type": "set_unit", "unit": "in"}, {"type": "add_box", "width": 48, "height": 18, "depth": 14}, {"type": "translate", "id": 1, "dx": 0, "dy": 0, "dz": 0}, {"type": "set_color", "id": 1, "r": 0.8, "g": 0.2, "b": 0.1} ]}' # Query scene state curl https://staging.krad.app/api/scene/PROJECT_ID/state \ -H "Authorization: Bearer YOUR_TOKEN" # Export as STL curl https://staging.krad.app/api/scene/PROJECT_ID/export/stl \ -H "Authorization: Bearer YOUR_TOKEN" -o scene.stl # Export as GLB (with textures) curl https://staging.krad.app/api/scene/PROJECT_ID/export/glb \ -H "Authorization: Bearer YOUR_TOKEN" -o scene.glb ``` ## WebSocket Connect for real-time bidirectional control (replace `https` with `wss` and `http` with `ws` in the origin scheme): ``` https://staging.krad.app/api/scene/PROJECT_ID/ws?token=YOUR_TOKEN ``` Send JSON commands, receive JSON events with updated scene state. ## Units Every dimension argument (width/height/depth/radius/x/y/z/etc.) is interpreted in the session's **active unit**, which defaults to millimeters. Call `set_unit` with `"mm" | "cm" | "m" | "in" | "ft"` to change it; subsequent commands take their numeric args in that unit. Do NOT pre-convert. `get_dimensions` reports back in the active unit too, so you never have to translate manually. ## Available Commands All commands are JSON objects with a `"type"` field. Send as an array in `{"commands": [...]}` to `/api/scene/PROJECT_ID/command`. This list is **generated from the SceneCommand schema** — every variant below is callable, and the parameter list reflects the wire contract. For the machine-readable spec see `https://staging.krad.app/api/openapi.json`. ### Primitives - `add_box`: `depth` (number), `height` (number), `width` (number). Add an axis-aligned box to the scene. - `add_cone`: `height` (number), `radius` (number). Add a cone with its apex along +Y. - `add_cylinder`: `height` (number), `radius` (number). Add a cylinder oriented along the Y axis. - `add_offset_plane`: `base` (string), `offset` (number). Add a user-created sketch plane parallel to one of the three base planes (`ground`, `front`, `right`) at the specified `offset` in the active unit. - `add_sphere`: `radius` (number). Add a sphere. - `add_torus`: `major_radius` (number), `minor_radius` (number). Add a torus. - `add_wedge`: `depth` (number), `height` (number), `width` (number). Add a triangular prism (isosceles triangle in XY swept along Z). ### Selection - `deselect_all`. Clear the current selection. - `select`: `id` (integer). Select an object by id. - `select_by_name`: `name` (string). Select an object by its display name. ### Transform - `rename`: `id` (integer), `name` (string). Rename an object. - `set_color`: `b` (number), `g` (number), `id` (integer), `r` (number). Set an object's display color. - `set_position`: `id` (integer), `x` (number), `y` (number), `z` (number). Set an object's world-space position (absolute, not delta). - `set_rotation`: `id` (integer), `pivot` ([number; 3], optional), `x` (number), `y` (number), `z` (number). Set an object's rotation as Euler angles in radians (Y→X→Z order). - `set_scale`: `id` (integer), `x` (number), `y` (number), `z` (number). Set an object's per-axis scale. - `translate`: `dx` (number), `dy` (number), `dz` (number), `id` (integer). Translate an object by a delta. ### Operations - `delete`: `id` (integer). Delete an object by id. - `delete_selected`. Delete every currently-selected object. - `duplicate`: `count` (integer), `id` (integer), `offset` ([number; 3], optional). Clone an object N times. ### Sketch + Extrude - `enter_sketch`: `plane` (string, optional). Enter sketch mode. - `exit_sketch`. Exit sketch mode. - `extrude_box`: `depth` (number), `height` (number), `width` (number), `x` (number), `z` (number). Draw a rectangle on the ground plane and extrude it. - `extrude_cylinder`: `height` (number), `radius` (number), `x` (number), `z` (number). Draw a circle on the ground plane and extrude it. - `extrude_profile`: `height` (number), `profile_id` (integer), `symmetric` (boolean, optional). Extrude a closed profile into a 3D solid. - `extrude_sketch`: `height` (number). Extrude the currently-selected sketch shape to the given height. - `revolve_profile`: `angle` (number, optional), `profile_id` (integer). Revolve a closed profile around an axis to produce a solid of revolution — turned legs, vases, bowls, anything with rotational symmetry. - `sketch_circle`: `cx` (number), `cy` (number), `radius` (number). Draw a circle centered at `(cx, cy)` with the given radius on the active sketch plane. - `sketch_line`: `closed` (boolean), `points` (array). Draw a polyline through the given 2D points on the active sketch plane. - `sketch_path`: `arc_segments` (integer, optional), `arcs` (array, optional), `origin_x` (number, optional), `origin_y` (number, optional), `plane` (string), `vertices` (array). Open a sketch context on the named plane. - `sketch_polygon`: `points` (array). Draw a closed polygon through the given 2D points. - `sketch_rectangle`: `x1` (number), `x2` (number), `y1` (number), `y2` (number). Draw a rectangle from corner `(x1, y1)` to corner `(x2, y2)` on the active sketch plane. ### Boolean - `boolean_intersect`: `a` (integer), `b` (integer). Keep only the volume shared by both objects. - `boolean_subtract`: `a` (integer), `b` (integer). Subtract object `b` from object `a`. - `boolean_union`: `a` (integer), `b` (integer). Union two objects into a new object. ### Joinery - `joint_dado`: `depth` (number), `face` (string), `inserter_ids` (array), `receiver_id` (integer). Cut a fixed-depth groove (dado) into `receiver_id` for each of the `inserter_ids` that crosses it. - `joint_half_lap`: `depth_fraction` (number), `face` (string), `inserter_ids` (array), `receiver_id` (integer). Cut half-lap notches into `receiver_id` where each of the `inserter_ids` crosses it on `face`. - `joint_mortise_tenon`: `face` (string), `inserter_face` (string), `inserter_id` (integer), `mortise_depth` (number), `mortise_height` (number), `mortise_width` (number), `receiver_id` (integer). Cut a mortise (rectangular hole) in `receiver_id` and grow a matching tenon out of `inserter_id`. - `joint_rabbet`: `depth` (number), `edge` (string), `face` (string), `receiver_id` (integer), `width` (number). Cut a rabbet (edge step) along one edge of `receiver_id`. ### Positioning - `align`: `anchor` (string), `axis` (string), `group` (boolean), `ids` (array), `target_anchor` (string, optional), `target_id` (integer, optional), `value` (number, optional). Move objects so a specified face/center aligns on one axis, either to a target object or to an absolute coordinate. - `snap`: `face` (string), `id` (integer), `offset` (number, optional), `target_face` (string), `target_id` (integer). Place one object's face flush against another's, with auto- centering on the two perpendicular axes — the joinery primitive. - `span`: `axis` (string), `from_id` (integer), `id` (integer), `to_id` (integer). Position an object centered between two reference objects on a single axis. - `stack`: `align_anchor` (string, optional), `align_axis` (string, optional), `axis` (string), `gap` (number), `ids` (array), `start_id` (integer, optional). Distribute objects in a row along an axis with a fixed edge-to-edge gap. ### Edge ops - `chamfer`: `id` (integer), `near` ([number; 3]), `width` (number). Chamfer the edge loop nearest `near` on `object_id`. - `cove`: `id` (integer), `near` ([number; 3]), `radius` (number). Cove (concave round-cut) the edge loop nearest `near` on `object_id`. - `fillet`: `id` (integer), `near` ([number; 3]), `radius` (number). Fillet (round-over) the edge loop nearest `near` on `object_id`. ### Materials - `get_object_material`: `id` (integer). Look up an object's current material assignment. - `remove_material`: `id` (integer). Clear an object's material assignment. - `set_material`: `id` (integer), `material` (string). Assign a library material to an object. - `set_texture_transform`: `id` (integer), `offset_x` (number, optional), `offset_y` (number, optional), `rotation` (number, optional), `scale_x` (number, optional), `scale_y` (number, optional). Adjust the UV transform on a textured object. - `toggle_texture`: `enabled` (boolean), `id` (integer). Toggle the per-object "sample texture maps" flag. ### Tags - `group_by_tag`: `tag` (string). Return the IDs of every object carrying `tag`. - `tag`: `id` (integer), `tag` (string). Attach a free-form string tag to an object. - `untag`: `id` (integer), `tag` (string). Remove a tag from an object. ### Inspection - `get_dimensions`: `id` (integer, optional). Read the world-space dimensions of one object or the whole scene. ### Camera - `set_view`: `angle` (string). Set the camera to a named orthographic view. - `zoom_to_fit`. Frame all visible objects in the viewport. ### Units - `set_unit`: `unit` (string). Set the session's active measurement unit. ### AI - `ai_generate_3d`: `image_object_id` (integer), `name` (string, optional). Generate a 3D mesh from an Image scene object using AI. - `ai_segment`: `image_object_id` (integer), `prompt` (string, optional). Segment objects in an image using AI. ### Save & Export - `export_glb`. Export the scene as binary GLB (glTF) with embedded textures. - `export_stl`. Export the scene as binary STL. - `save_project`: `name` (string, optional). Update the project's snapshot (and optionally its name). ### Visual - `snapshot`: `height` (integer, optional), `orbit_x` (number, optional), `orbit_y` (number, optional), `size` (integer, optional), `view` (string, optional), `width` (integer, optional). Render the current scene as a PNG and return it. ### Other - `create_project`: `name` (string). Create a new empty project owned by the calling user. - `list_projects`. List the calling user's existing projects. - `loft`: `profile_a` (integer), `profile_b` (integer). Loft between two closed sketch profiles. - `mirror`: `axis` (string), `id` (integer), `keep_original` (boolean), `offset` (number, optional). Mirror an object across a world-axis plane, producing a new reflected object. ## Tips for AI Agents 1. **If the user gives you a project ID, use it directly as the session ID in all endpoints** (`/command`, `/upload`, `/export/stl`, etc.). Do NOT call `/api/scene/create` — that creates a separate project. Scene state loads automatically from the saved snapshot. Only call create when you need a brand new blank project and the user hasn't given you a project ID. 2. After each command batch, the response includes the full `objects` array with IDs, names, positions, colors, `has_mesh`, `vertex_count`, `triangle_count`, `aabb` (world-space bounding box in **mm**), and `size_in_unit` + `unit` (the same bbox size in the session's **active unit**). Use `has_mesh: true` to confirm geometry is ready for export, and read `size_in_unit` to verify placement/scale in the unit you're working in — no `get_dimensions` round-trip needed. (If you set the unit to inches and a part reports `size_in_unit: [1.25, 0.75, 48]`, it's a 1.25" slat; if it reads `[0.049, ...]` you stored mm by mistake.) 3. **Booleans vs joints**: `boolean_*` operations consume both source objects and produce a new ID (hidden sources, new result). `joint_half_lap` / `joint_dado` / `joint_rabbet` / `joint_mortise_tenon` keep the receiver's ID stable — receiver mesh is updated in place. Prefer joints for joinery (a bench's 22 half-laps become one call); prefer booleans when you genuinely want a merged or carved-up new shape. - **Holes are a subtract, not a profile option.** There's no holed-`sketch_path`. For a plate with a hole / a washer: extrude the outer profile into a solid, extrude the hole profile into a second solid spanning the thickness, then `boolean_subtract` the hole from the plate. The hole stays a real CSG op you can edit later. 4. **Positioning without world-coord math**: `snap` (face-to-face flush placement), `align` (coplanar alignment on one axis), `stack` (distribute with gap), and `span` (place between two references) compose relative joinery without computing absolute coords. `translate` with `{id, dx, dy, dz}` shifts by a delta in the active unit. Pair these with `joint_*` to assemble parts, then cut joinery on top. - **`align` has two modes — pick the right one or you'll collapse parts.** By default `align` moves EACH object's anchor to the target ("make these faces coplanar"). Aligning a row of distinct parts (e.g. 11 spread slats) to one `value` puts every part at that coordinate — they stack on top of each other. To shift a set as a rigid GROUP (preserving the spacing between its parts) so the group's combined edge/center lands on the target, pass **`group: true`**. Use `group: true` for "center this whole assembly at x=0" / "move this group so its left edge is at X"; use the default for "make these N faces flush." If a non-group align collapses ≥3 spread parts, the response includes a warning effect suggesting `group: true`. 5. **Rotation around a pivot**: `set_rotation` takes an optional `pivot: [x, y, z]` (world-space, active unit) so a leg can rotate around its top edge instead of its center. - **Patterns are composed, not separate tools.** A LINEAR array is `duplicate {count, offset}` (one call). A CIRCULAR array is `duplicate {count}` then `set_rotation` each copy with `pivot` at the ring center, stepping the angle per copy — reference the copies in a `batch` via `$N.0`, `$N.1`, … (the M-th new id of the duplicate). There is no `mirror` over MCP yet (a reflection needs winding-correct handling that negative scale doesn't give you) — say so rather than attempting `set_scale` with a negative factor, which would invert the mesh. 6. Object IDs are sequential starting from 1. After a boolean, sources become hidden (`visible: false`) and the result gets a new ID. Joints don't shift IDs. 7. The coordinate system is Y-up. The ground plane is Y=0. Primitives are added with their center at `(0, height/2, 0)` (sitting on the ground). 8. STL export converts to Z-up and scales geometry to the active unit. 9. **Always `save_project` after meaningful work.** save_project persists your scene to the cloud (S3 + database). The response includes a project URL the user can open in their browser. - **Share the project URL with the user** — `https://staging.krad.app/projects/{project_id}` — proactively, not just when asked. They can open it to see your work. - **Reuse one project; don't create a new one per attempt.** To start over, clear the scene with `delete` / `delete_selected`. Spinning up a fresh `create_project` for every retry litters the user's `/projects` list with orphaned half-built projects. Only create a new project for a genuinely separate deliverable. - **For many cuts, union the cutters into one body then do a single subtract.** It leaves a cleaner result tree and fewer hidden intermediate objects than subtracting cutters one at a time. 10. **Keep sessions alive during long AI jobs.** `ai_generate_3d` can take 1-6 minutes (GPU cold start + generation). While polling `GET /api/scene/{id}/jobs/{job_id}`, also ping `GET /api/scene/{id}/state` every 30 seconds. 11. **Visual feedback**: call `snapshot` to see the scene rendered. The server auto-picks an aspect ratio matching the scene's projected extents on the camera plane — long benches don't collapse to a line at 1024². Pass explicit `width`/`height` to override. ## Endpoints | Method | Path | Description | |--------|------|-------------| | POST | /api/scene/create | Create session | | GET | /api/scene/sessions | List your sessions | | DELETE | /api/scene/sessions | Delete all your sessions | | POST | /api/scene/{id}/command | Execute commands | | GET | /api/scene/{id}/state | Query state | | POST | /api/scene/{id}/upload | Upload image for AI ops | | GET | /api/scene/{id}/export/stl | Download STL | | GET | /api/scene/{id}/export/glb | Download GLB | | GET | /api/scene/{id}/jobs/{job_id} | Poll async job status | | GET | /api/scene/{id}/ws?token=... | WebSocket | | DELETE | /api/scene/{id} | Destroy session | ## OpenAPI Spec Machine-readable spec at: `https://staging.krad.app/api/openapi.json` ## Rate limits AI operations are rate-limited per tier: - `ai_generate_3d`: 5 jobs/hour on the free tier - `ai_segment`: 30 calls/hour on the free tier Non-AI commands (`add_*`, `set_*`, `boolean_*`, `joint_*`, etc.) are currently unmetered. Save bandwidth by batching commands into one `POST /command` rather than sending each separately. ## User-facing features The Scene API exposes the programmable surface. The user-facing UI has additional features (Generate 3D from image, Solidify hollow mesh, etc.) documented separately at `https://staging.krad.app/help` — useful when the user asks the agent to "do the thing in the UI" and the agent needs to know what's required (WebGPU, AI quota, etc.) and how to advise. Index: `https://staging.krad.app/help` ### Reference - [The HUD pillbar](https://staging.krad.app/help/editor-hud-pillbar): Floating mono strip at the bottom-center of the viewport — current mode, object count, snap state, cursor coords, FPS, and the units selector. - [The left rail](https://staging.krad.app/help/editor-left-rail): Primary tool palette along the left edge of the viewport. Categorized in baseline mode, swaps to sketch tools when sketching. - [The krad editor](https://staging.krad.app/help/editor-readme): Visual tour of the editor's chrome — top bar, left rail, right dock, HUD pillbar, view controls. Each piece has its own page. - [The right dock](https://staging.krad.app/help/editor-right-dock): Right column of the editor — items panel (sketch + objects), history panel, and a properties inspector that pops up beside selections. - [Keyboard shortcuts](https://staging.krad.app/help/editor-shortcuts): Every keyboard shortcut the editor recognizes — general, sketch mode, view, sketch tools — opened with the ? key. - [The editor top bar](https://staging.krad.app/help/editor-top-bar): Top floating chrome bar — project name + cloud save + export, share + fork, avatar / login. Two distinct clusters. - [View controls](https://staging.krad.app/help/editor-view-controls): Floating horizontal strip at the bottom-left of the canvas — undo / redo, zoom-to-fit, home view, clip plane toggle, perspective/ortho toggle. - [Export STL](https://staging.krad.app/help/export-stl): Download the current scene as a printable STL file. - [Generate 3D from image](https://staging.krad.app/help/generate-3d): Convert a 2D image into a textured 3D mesh. - [Materials & textures](https://staging.krad.app/help/materials): Apply real-world materials (birch plywood, 6061 aluminum, walnut, …) to objects in the scene. - [Solidify hollow mesh](https://staging.krad.app/help/solidify): Turn a hollow surface into a watertight solid.