@haitch-diy/sdk
v0.0.1
Published
Official TypeScript SDK for the Haitch developer API — AI-native hardware product creation across System, CAD, Canvas, Firmware, and Electronics workspaces, plus Store, Partners, files, artifacts, and BOM sourcing.
Maintainers
Readme
Haitch TypeScript SDK
@haitch-diy/sdk is the official TypeScript SDK for the Haitch developer API — an
AI-native platform for hardware product creation across System, CAD, Canvas,
Firmware, and Electronics workspaces, with Store, Partners, files, artifacts, and
BOM sourcing.
Preview (
0.x). The public contract is stabilizing; minor versions may include breaking changes until a1.0release. See VERSIONING.md.
Install
npm install @haitch-diy/sdkCreate a developer API key from your Haitch developer dashboard, then keep it in a server-side environment variable. The SDK is for server-side use only — never embed an API key in a browser or any client the end user controls.
import { HaitchClient } from "@haitch-diy/sdk";
// baseUrl defaults to the hosted Haitch API origin; override only if directed to.
const client = new HaitchClient({
apiKey: process.env.HAITCH_API_KEY!,
});
const response = await client.responses.create({
workspace: "system",
type: "validate_system",
input: {
graph: {
product_id: "greenhouse_monitor_quickstart",
product_name: "Connected Greenhouse Monitor",
root_assembly: {
id: "asm_greenhouse_monitor",
name: "Connected Greenhouse Monitor",
children: [
{ id: "controller_pcb", name: "ESP32 Sensor Controller PCB" },
{ id: "environment_sensor", name: "Temperature and Humidity Sensor" },
],
},
},
},
});
for await (const event of client.responses.stream(response.id)) {
console.log(event.type, event.message);
if (event.type === "response.artifact.created") {
console.log(event.payload?.artifact);
}
}Firmware, CAD, Canvas, and Electronics response routes are project-bound. Use
projects.createAndGenerate(...) when you want the API to create a fresh
workspace first, or pass project_id when generating inside an existing
workspace:
const generatedFirmware = await client.projects.createAndGenerate("firmware", {
project: { name: "ESP32 environmental sensor" },
description: "Generate firmware for an ESP32 environmental sensor",
});
await client.firmware.create("Add a low-power telemetry mode", {
project_id: generatedFirmware.project.id,
});Imported STEP workflows
Upload a model/step file with the existing Files API, then pass its file_id
to the CAD STEP helpers. These request and response fields intentionally preserve
the public API's snake_case contract.
const file = await client.files.create({
purpose: "cad_import",
filename: "reference.step",
content_type: "model/step",
content_base64: stepFileBase64,
});
const imported = await client.cad.importStep({ file_id: file.id });
const digest = await client.cad.stepImports.digest({
analysis_storage_id: imported.analysis_storage_id,
});
const mode = await client.cad.classifyInstruction({
user_instruction: "Create a screw-on enclosure around this reference part.",
});
if (mode.mode === "derive") {
const derived = await client.cad.derivedParts.create({
source_step_storage_id: imported.step_storage_id,
analysis_storage_id: imported.analysis_storage_id,
user_instruction: "Create a screw-on enclosure around this reference part.",
design_intent: { wall_thickness: 2, clearance: 0.5, closure: "screw_lid" },
});
console.log(derived.step_url, digest.reference_digest);
}Generated OpenAPI contract helpers are exported from the package. They are
regenerated from openapi/haitch-v1.yaml and are useful for request builders,
docs checks, and compile-time coverage assertions:
import {
OPENAPI_PATHS,
OPENAPI_WORKSPACES,
type HaitchOpenApiPath,
type HaitchOpenApiWorkspace,
} from "@haitch-diy/sdk";
const path: HaitchOpenApiPath = "/v1/responses/{responseId}/events";
const workspace: HaitchOpenApiWorkspace = OPENAPI_WORKSPACES[0];
console.log(path, workspace, OPENAPI_PATHS.length);A runnable quickstart ships in the package at examples/quickstart.mjs. It validates
a compact System graph response, reads events, and can optionally wait for the terminal
state plus temporary artifact downloads:
HAITCH_API_KEY=hd_test_... node quickstart.mjs --wait-terminal --require-artifactsCompleted projectless System quickstart responses register downloadable response
artifacts from their output graph/document/validation payloads, so --require-artifacts
verifies the same artifact-download contract as project-backed workspace responses.
Project helpers let API-key owners inspect and manage existing workspaces before starting generation jobs that need a concrete project id:
const allCadProjects = await client.projects.list({ workspace: "cad", limit: 20 });
const firmwareProjects = await client.projects.listForWorkspace("firmware", { limit: 20 });
const createdProject = await client.projects.create("cad", { name: "New enclosure" });
const generatedProject = await client.projects.createAndGenerate("cad", {
project: { name: "Generated enclosure" },
prompt: "Create a compact electronics enclosure",
});
const project = await client.projects.retrieve("cad", "project_123", { include_state: true });
const updateResult = await client.projects.update("cad", "project_123", { name: "Updated enclosure" });
const rebuild = await client.projects.update("firmware", "firmware_project_123", { files: firmwareFiles });
if (rebuild.object === "project_rebuild") {
await client.projects.retrieveRebuild("firmware", "firmware_project_123", rebuild.id);
}
await client.projects.delete("cad", "project_123");
console.log(allCadProjects.data.length, firmwareProjects.data.length, createdProject.id, generatedProject.response.id, project.workspace, updateResult.object);These helpers call /v1/projects,
/v1/workspaces/{workspace}/projects, and
/v1/workspaces/{workspace}/projects/{projectId} for System, CAD, Canvas,
Firmware, and Electronics. Reads require the corresponding {workspace}:read
scope; creation requires {workspace}:create and creates a project container
without enqueueing generation; createAndGenerate() requires
responses:create plus {workspace}:create, rejects preexisting project ids,
and returns { project, response }; updates accept {workspace}:write or
existing {workspace}:create generation scope, and deletes require explicit
{workspace}:delete plus owner access enforced by the public API.
Metadata-only updates return HaitchProject. Source-code patches return
ProjectRebuild and start the same rebuild/promotion flow used by the web
workspace: Firmware files, Electronics tscircuitSource / source, and
CAD currentData.scriptCode / currentData.stepCadCode patches. CAD source
patches run headlessly for three.js, manifold, JSCAD, replicad.js,
opencascade.js, and rhino3dm.js; cadquery remains supported through the Python compile path.
Poll rebuilds with client.projects.retrieveRebuild(...).
System graph authoring is operation-based and versioned. Generic projects.update("system", ...) graph replacement remains a compatibility/promotion path for existing integrations; it is not the recommended authoring surface. Retrieve the project with include_state: true to obtain its graph and version, preview a batch, then apply it. Protected or destructive changes must reuse the returned preview id:
const project = await client.projects.retrieve("system", "system_123", { include_state: true });
const operations = [
{
op: "add_node" as const,
parentId: "asm_controls",
node: { name: "Temperature sensor", type: "part_offtheshelf", quantity: 1 },
},
{
op: "upsert_node_parameter" as const,
nodeId: "prt_sensor",
key: "measurement_range",
value: { value: 125, unit: "C" },
},
];
const preview = await client.system.graph.previewOperations("system_123", {
base_version: project.version!,
operations,
});
const result = await client.system.graph.applyOperations("system_123", {
base_version: project.version!,
operations,
idempotency_key: crypto.randomUUID(),
...(preview.requires_confirmation
? { confirmation: { preview_id: preview.preview_id } }
: {}),
});
console.log(result.version, result.diff);The SDK exposes discriminated SystemGraphOperation request types and typed preview/result responses. The shorter preview() and apply() aliases preserve the same semantics. Conflict and confirmation details remain structured:
try {
await client.system.graph.applyOperations("system_123", request, {
idempotencyKey: "graph-edit-7-sensor",
});
} catch (error) {
if (error instanceof HaitchApiError && error.code === "system_graph_version_conflict") {
const currentVersion = error.details?.currentVersion;
// Refetch, review the intervening change, and create a new preview explicitly.
console.log("Current graph version", currentVersion);
}
if (error instanceof HaitchApiError && error.code === "system_graph_protected_change_requires_confirmation") {
// Preview the exact batch and resubmit it with confirmation.preview_id.
}
}Workspace helpers are thin wrappers over the dedicated workspace endpoints:
await client.system.create("Design a wearable air-quality monitor");
await client.system.validateGraph({
mode: "strict",
graph: { root_assembly: { id: "root", children: [] } },
});
await client.system.createHandoffPackage({
graph: { root_assembly: { id: "root", children: [] } },
});
await client.system.exportBundle({
project_name: "Sensor",
graph_version: 1,
graph: { root_assembly: { id: "root", children: [] } },
});
await client.system.sourceParts("system_123", {
part: { id: "part_1", name: "Humidity sensor" },
quality_mode: "high_confidence",
});
await client.system.listSources("system_123", { limit: 25 });
await client.system.saveSource("system_123", {
part_id: "part_1",
source: { vendor: "Digi-Key", url: "https://example.test/part" },
selected_by_action: "developer_api",
});
await client.system.submitSourceFeedback("system_123", {
action: "selected",
part: { id: "part_1", name: "Humidity sensor" },
selected: { vendor: "Digi-Key" },
reason: "best availability",
});
const cadResponse = await client.cad.create("Revise the enclosure as a selectable multi-part assembly", {
project_id: "project_123",
});
if (cadResponse.output?.assemblyManifest) {
console.log(cadResponse.output.engineRouting?.tradeOffs);
console.log(cadResponse.output.fidelityScore, cadResponse.output.referenceKind);
console.log(cadResponse.output.assemblyManifest.parts);
}
await client.canvas.create({
prompt: "Create product listing media",
mode: "design",
}, { project_id: "canvas_123" });
await client.canvas.create({
prompt: "Create an exploded assembly view",
mode: "exploded_view",
sourceArtifactId: "artifact_reference_image",
}, { project_id: "canvas_123" });
await client.canvas.generate3dModel({
input: {
request_type: 3,
generation_mode: "image",
model: "hitem3dv1.5",
format: "glb",
sourceArtifactId: "artifact_reference_image",
},
});
await client.canvas.generateTextureModel({
input: {
request_type: 3,
generation_mode: "image",
model: "hitem3dv2.0",
format: "glb",
pbr: 1,
sourceArtifactId: "artifact_reference_image",
mesh_url: "https://files.example/reference-mesh.glb",
},
});
await client.firmware.create("Generate ESP32 firmware");
await client.electronics.create("Generate the PCB", { project_id: "electronics_123" });
await client.electronics.validatePcb("Run PCB validation", { project_id: "electronics_123" });
const electronicsCompile = await client.electronics.compile({
project_id: "electronics_123",
source: "export default () => <board width={20} height={20} />",
});
await client.electronics.retrieveCompile(electronicsCompile.id, { from: 0 });
await client.electronics.logs(electronicsCompile.id);
await client.electronics.searchComponents({ q: "esp32", limit: 5 });
await client.electronics.resolveComponent({
component_ref: "U1",
chip_name: "ESP32-WROOM",
});
await client.electronics.sourceParts("electronics_123", {
part: { id: "U2", name: "Humidity sensor" },
source: "jlcpcb",
limit: 5,
});
await client.electronics.listSources("electronics_123", { limit: 25 });
await client.electronics.saveSource("electronics_123", {
part_id: "U2",
part_name: "Humidity sensor",
source: { vendor: "LCSC", lcsc: "C123" },
selected_by_action: "developer_api",
});
await client.electronics.artifacts("electronics_123", { stage: "pcb", limit: 20 });
await client.electronics.validationReports("electronics_123", { stage: "pcb" });
await client.electronics.bom("electronics_123");
// Workspace-neutral BOM artifact and sourcing helpers
const bom = await client.bom.retrieve("cad", "cad_project_123");
const candidates = await client.bom.searchSources("cad", "cad_project_123", { row: bom.rows[0] });
await client.bom.saveSource("cad", "cad_project_123", {
part_id: String(bom.rows[0].id),
row_stable_key: String(bom.rows[0].stable_key),
bom_revision_id: bom.revision_id,
source: candidates.source as Record<string, unknown>,
});
await client.electronics.manufacturingPackages("electronics_123");Those helpers call /v1/system/graphs, /v1/system/graphs/validate,
/v1/system/graphs/handoff-package, /v1/system/graphs/export-bundle,
/v1/system/projects/{projectId}/sources/search,
/v1/system/projects/{projectId}/sources,
/v1/system/projects/{projectId}/sources/feedback,
/v1/cad/generations, /v1/canvas/designs, /v1/canvas/models,
/v1/firmware/builds, and /v1/electronics/designs while
client.responses.create() remains available for the generic /v1/responses
facade. Canvas design responses support executable input.mode values
design, sketch, engineering_drawing, exploded_view, video, cmf,
multiview, design_story, and engineering_analysis; every mode except
design requires a source image, image group, or attachment reference.
Hitem3D geometry and textured model generation use client.canvas.generate3dModel
and client.canvas.generateTextureModel; model-task examples pass provider-valid
model enums explicitly (hitem3dv1.5 for geometry and hitem3dv2.0 for the
PBR texture path) so executable smoke runs match the server-side validation
contract. Product design suggestions and CMF extraction
are not separate /v1/canvas/designs modes in this SDK; use the supported
design or cmf modes, or the application-specific internal tools when you
need those analysis-only workflows. Electronics compile helpers call
/v1/electronics/compiles plus status and log endpoints.
Electronics component search and resolution call
/v1/electronics/components/search and
/v1/electronics/components/resolve. Electronics project deliverable helpers
read persisted artifacts, validation reports, BOM rows, and manufacturing
packages from /v1/electronics/projects/{projectId}/*. First-turn Electronics
generation should use client.electronics.create(...) without a type, or with
type: "generate_schematic", so server-side confirmation gates are
auto-confirmed for API callers; generate_pcb is a later-stage command once
source artifacts exist. Electronics BOM source
helpers call /v1/electronics/projects/{projectId}/sources/search and
/v1/electronics/projects/{projectId}/sources.
Runtime capabilities can be discovered with:
const capabilities = await client.capabilities.retrieve();
console.log(capabilities.workspaces.electronics.response_endpoint);The Canvas mode example dry-runs payloads by default and only calls the API
when --execute is passed:
node canvas-modes.mjs --help
HAITCH_API_KEY=hd_test_... node canvas-modes.mjs --execute --canvas-id canvas_123 --source-artifact-id artifact_123Files and artifact metadata are available through the same API-key client:
const file = await client.files.create({
purpose: "reference_image",
filename: "reference.png",
content_type: "image/png",
content_base64: "base64-content",
});
const files = await client.files.list({ purpose: "reference_image", limit: 20 });
const registered = await client.artifacts.register({
file_id: file.id,
response_id: "promoted:example",
workspace: "canvas",
type: "image",
metadata: { source: "public_project_promotion" },
});
const artifact = await client.artifacts.download("artifact_123");
console.log(file.url, files.data.length, registered.id, artifact.download_url);
const artifacts = await client.artifacts.list({ workspace: "cad", limit: 20 });
console.log(artifacts.data.map((item) => item.filename));Firmware compile helpers are available when you already have an owned Firmware project and source files:
const compile = await client.firmware.compile({
project_id: "firmware_project_123",
compiler: "platformio",
board_config: {
platform: "esp32",
board: "esp32dev",
framework: "arduino",
},
files: [
{
path: "src/main.cpp",
content: "void setup(){} void loop(){}",
},
],
});
const status = await client.firmware.retrieveCompile(compile.id, {
include_logs: true,
from: 0,
});
const binary = await client.firmware.binary(compile.id);
console.log(status.status, binary.download_url);
const boards = await client.firmware.boards.list({ platform: "esp32" });
const platforms = await client.firmware.platforms.list();
const libraries = await client.firmware.libraries.search({
query: "adafruit",
source: "platformio",
limit: 5,
});
console.log(boards.data.length, platforms.platforms.length, libraries.data.length);Completed responses also include registered storage-backed artifacts when the workflow output contains durable storage ids:
const response = await client.responses.retrieve("resp_123");
for (const artifact of response.artifacts ?? []) {
console.log(artifact.id, artifact.type, artifact.filename);
}Store and Partner helpers are also available:
await client.store.products.list({ limit: 20 });
await client.partners.listDirectory({ sort: "match", limit: 10 });
const product = await client.store.products.createDraft({
project_id: "project_123",
title: "Sensor kit",
description: "A compact environmental sensor kit.",
images: ["storage_image_id"],
});
await client.store.products.update(String(product.data._id), {
description: "Updated product description.",
});
await client.store.products.submit(String(product.data._id));
await client.store.products.createInterestLead(String(product.data._id), {
email: "[email protected]",
source: "launch_page",
});
await client.store.leads.list({ product_id: String(product.data._id), limit: 20 });
await client.store.orders.list({ limit: 20 });
await client.store.reservations.list({ product_id: String(product.data._id), limit: 20 });
await client.store.metrics.retrieve();
const partnerRequest = await client.partners.createRequest({
source_workspace: "electronics",
source_project_id: "electronics_123",
title: "Prototype PCB",
request_type: "manufacturing_rfq",
requested_deliverables: ["PCB assembly quote"],
});
await client.partners.requests.list({ limit: 20 });
await client.partners.inviteToRequest(String(partnerRequest.data.request._id), {
partner_id: "partner_123",
invitation_kind: "rfq",
message: "Can you quote this prototype build?",
});
await client.partners.respondToInvitation("invitation_123", {
response: "accepted",
});
const partnerProfile = await client.partners.profiles.createDraft({
name: "Board House",
partner_types: ["manufacturer"],
});
await client.partners.profiles.update(String(partnerProfile.data._id), {
service_summary: "Prototype PCB assembly and enclosure quoting.",
});
await client.partners.profiles.submit(String(partnerProfile.data._id));
await client.partners.requests.addArtifact(String(partnerRequest.data.request._id), {
source_workspace: "electronics",
source_project_id: "electronics_123",
artifact_type: "bom",
storage_id: "storage_id",
});
await client.partners.rooms.list({ limit: 10 });
await client.partners.rooms.sendMessage("room_123", {
text: "Thanks for the quote.",
});
await client.partners.rooms.submitFeedback("room_123", {
ratings: { overall: 5, communication: 5 },
reported_outcome: "quote_received",
would_use_again: true,
});
await client.partners.rooms.close("room_123");
await client.partners.reportProfile("partner_123", {
reason: "misleading_profile",
});
await client.partners.rooms.report("room_123", {
reason: "spam",
message_id: "message_123",
});Store and Partner write helpers use the same public API validation as raw HTTP and MCP calls. Oversized or deeply nested commercialization payloads are rejected before they are persisted; Partner artifact and room attachment URLs must be HTTP(S), and room attachments must include a storage id or URL.
Usage can be retrieved without consuming quota:
const usage = await client.usage.retrieve();
console.log(usage.count, usage.remaining, usage.limit);This package intentionally covers only supported endpoints. Store checkout, payout, and settlement operations plus Partner approval/moderation are not part of the first-launch contract. The hosted MCP connection renders shared project, response, artifact, and BOM views over the same public API this SDK uses, so no SDK helper is needed for view state.
