---
title: "Upsert an assembly"
method: POST
path: "/public/v1/assemblies"
tags: ["Assembly"]
---

# Upsert an assembly

`POST /public/v1/assemblies`

Create, update, or delete an assembly and its outputs, inputs, and costs in a single request.

Every row — the assembly and each nested output, input, and cost — carries a required `action` of
`CREATE`, `UPDATE`, or `DELETE`. `UPDATE` and `DELETE` must include the row's `id`; `CREATE` omits
it. `DELETE` on the assembly removes it along with all of its outputs, inputs, and costs. Updates
are sparse: only the fields you send are changed. A nested row (output, input, or cost) you leave
out entirely is left untouched — omission never deletes it; removing one always requires sending it
with `action` `DELETE`. The whole request is applied atomically: if any row is rejected, none of the
changes are saved.

How this moves inventory: while the assembly is `PENDING`, each fulfilled input (`status` `PENDING`
or `COMPLETED`) claims specific on-hand inventory from its batch or package, and each `DRAFT` input
reserves product-level quantity without committing a specific lot. Completing an output consumes its
inputs and produces the output into inventory at its `location_id` — as a batch for batch-/product-
tracked outputs, or as a new package for package-tracked (Metrc) outputs. Completing the whole
assembly requires all of its outputs to be completed.

How this moves compliance: on a Metrc license, completing a package-tracked output (or a Metrc
processing job) is pushed to Metrc as a side effect after the request commits. A 2xx therefore means
the change was saved in Distru, not that Metrc has finished syncing — the created packages' Metrc
identifiers may still be absent and the affected packages briefly flagged as syncing. Re-fetch the
assembly with `GET /public/v1/assemblies/{id}` to observe the synced result. This endpoint supports
Metrc and non-compliance (NONE) licenses only; BioTrack is not supported.

Only assemblies with `creation_source=MANUALLY_CREATED` (i.e. created via the Assembly form in
Distru or via the API) can be modified or deleted by this endpoint; system-generated assemblies
(`SALES_ORDER`, `SPLIT_PACKAGE`, `LAB_TESTING`) are rejected.

Metrc processing jobs: set `metrc_processing_job.name` and `metrc_processing_job.type_id` together
to make the assembly a Metrc processing job. Both are required together, the license must be a
Metrc license with processing-job capability, `type_id` must be the Metrc ID of an existing Metrc
processing job type, and `name` must be non-empty and not already used by a processing job in Metrc.
Once set, `name` and `type_id` are permanent — they
cannot be changed on a later update; only `notes` and `waste` stay editable. `metrc_processing_job.id`
in the response is the job's Metrc-assigned ID (set by Metrc once Distru creates the job there); it
is read-only and null until then. Completing a processing job (setting `status` to `COMPLETED`) requires
`notes`. `waste` is what Distru reports to Metrc when the job is finished (each quantity sent with
its unit name); record it while the assembly is still `PENDING` — at the latest in the same request
that completes it. Once the assembly is `COMPLETED` the waste fields are read-only.

Completed assemblies: once an assembly's `status` is `COMPLETED`, the only assembly-level fields
you can still change are `description`, `custom_data`, `estimated_work_hours`,
`estimated_work_minutes`, and `owner_id`; every other assembly field is read-only, and the assembly
can be neither un-completed nor deleted. Its outputs and inputs are frozen — they cannot be edited
or deleted. Costs behave differently: an existing cost on a completed output cannot be edited or
deleted, but you can still add new costs to that output. Completing an assembly requires all of its
outputs to be completed. For a Metrc processing job, packages (inputs) cannot be added or removed
once any output is completed; to delete such an assembly, finish the job in Metrc and Distru removes
it automatically about 30 minutes later.

Required permission: `assemblies_permissions_create` to create, `assemblies_permissions_edit`
to update, `assemblies_permissions_delete` to delete.

## Request body

- UpsertAssemblyRequest — An assembly to create, update, or delete, with its outputs, inputs, and costs
  - `action` 'CREATE' | 'UPDATE' | 'DELETE', required — CREATE, UPDATE, or DELETE. Required. DELETE removes the assembly and all of its outputs, inputs, and costs.
  - `custom_data` object — A map of custom field IDs to their values for this assembly. Use GET /public/v1/custom-fields?parent_object=assembly to retrieve the available fields, their IDs, and their types. The value format depends on the field's type: a text field takes a string, a date field takes a full ISO8601 datetime, and a checkbox field takes an array of its selected options. On update this replaces the whole custom-data map, so send every field you want to keep.
  - `description` string — A free-text description for this assembly. Editable at any status.
  - `estimated_start_datetime` string — When this assembly is planned to start, as an ISO 8601 datetime (e.g. `2026-08-19T00:00:00Z`). Optional; omit to leave it unset.
  - `estimated_work_hours` integer — The whole-hours portion of the estimated work time; must be 0 or greater. Combine with `estimated_work_minutes` for the full estimate (e.g. 1 hour 30 minutes is `estimated_work_hours` 1, `estimated_work_minutes` 30). Editable at any status.
  - `estimated_work_minutes` integer — The minutes portion of the estimated work time; must be 0 or greater. Pairs with `estimated_work_hours` (see above). Editable at any status.
  - `id` string — The assembly to update or delete. Required for UPDATE and DELETE; omit for CREATE.
  - `metrc_processing_job` UpsertAssemblyMetrcProcessingJob — The Metrc processing job details for an assembly
    - `name` string — The Metrc processing job name. Required together with `type_id`; must be non-empty and not already used by a processing job in Metrc. Permanent once set — cannot be changed on a later update.
    - `notes` string — The Metrc processing job notes. Required to complete (status COMPLETED) a job. Editable after the job is created.
    - `type_id` integer — The Metrc ID of an existing Metrc processing job type. Required together with `name`. Permanent once set — cannot be changed on a later update.
    - `waste` UpsertAssemblyMetrcProcessingJobWaste — Waste reported to Metrc when finishing a Metrc processing job. Each quantity must be sent with its unit name. Submit it while the assembly is still PENDING (at the latest in the request that sets status to COMPLETED); it is read-only once the assembly is COMPLETED.
      - `count_quantity` number — Count-based waste. Required together with `count_unit_name`.
      - `count_unit_name` string — The Metrc unit name for `count_quantity`, e.g. "Each".
      - `volume_quantity` number — Volume-based waste. Required together with `volume_unit_name`.
      - `volume_unit_name` string — The Metrc unit name for `volume_quantity`, e.g. "Milliliters".
      - `weight_quantity` number — Weight-based waste. Required together with `weight_unit_name`.
      - `weight_unit_name` string — The Metrc unit name for `weight_quantity`, e.g. "Grams".
  - `outputs` UpsertAssemblyOutput[] — The outputs this assembly produces, each with its own inputs and costs. Sparse on update: an output you omit is left untouched; remove one by sending it with `action` `DELETE`.
    - `action` 'CREATE' | 'UPDATE' | 'DELETE', required — CREATE, UPDATE, or DELETE. Required.
    - `batch_id` string — The batch this output belongs to. Product-tracked and package-tracked: always omit it. Batch-tracked: optional — set it to an existing batch's ID and the output quantity lands in that batch, or leave it blank and the output quantity lands in a new batch (named by `batch_number`).
    - `batch_number` string — The Distru batch number recorded on the output (1-255 characters). Batch-tracked: names the new batch created when `batch_id` is omitted. Package-tracked: sets the created package's Distru batch number.
    - `bin_ids` string[] — The bins to store the output in, applied when the output is completed (requires bin inventory tracking enabled for the company). Package-tracked: sets the bins on the package Distru creates. Batch-tracked: replaces the batch's current bin set — an empty array clears it. Not applicable to product-tracked outputs.
    - `compliance_label` string — Package-tracked (Metrc) outputs only. The Metrc tag for the created package; must be an available tag in the output's license. Required to complete the output.
    - `compliance_quantity` number — Package-tracked (Metrc) outputs only. The output quantity in the package's Metrc unit — the unit type of the package's Metrc item, which comes from `metrc_item_id` (or from the input package's item when `use_same_item=true`). `quantity` is derived from it by unit conversion. Send this when creating a package-tracked output.
    - `copy_custom_data_from_input` boolean — Copy custom field values from the input onto this output.
    - `costs` UpsertAssemblyCost[] — The costs added directly to this output.
      - `action` 'CREATE' | 'UPDATE' | 'DELETE', required — CREATE, UPDATE, or DELETE. Required.
      - `cost_per_unit` number — The per-unit rate applied to this cost. Optional.
      - `cost_type_id` string — The cost type this cost is an instance of. Required when creating. Use GET /public/v1/cost-types to list the available cost types and their IDs.
      - `description` string — A free-text description for this cost. Optional.
      - `id` string — The cost to update or delete. Required for UPDATE and DELETE; omit for CREATE.
      - `quantity` number — The quantity of this cost; must be greater than 0. Required when creating.
    - `expiration_date` string — Package-tracked (Metrc) outputs only. The expiration date reported to Metrc for the created package, e.g. "2027-08-19".
    - `id` string — The output to update or delete. Required for UPDATE and DELETE; omit for CREATE.
    - `inputs` UpsertAssemblyInput[] — The inputs consumed to produce this output.
      - `action` 'CREATE' | 'UPDATE' | 'DELETE', required — CREATE, UPDATE, or DELETE. Required.
      - `batch_id` string — The source batch. Required for a fulfilled batch-tracked input — its product is derived from it — and must be omitted for package/product-tracked and DRAFT inputs.
      - `compliance_quantity` number — The consumed quantity in the package's compliance unit (Metrc). Required for a fulfilled package-tracked input; must be omitted otherwise (including DRAFT).
      - `id` string — The input to update or delete. Required for UPDATE and DELETE; omit for CREATE.
      - `location_id` string — The location the input is drawn from. Required for batch-tracked, product-tracked, and DRAFT inputs; omit for a package-tracked input (derived from the package).
      - `package_id` string — The source package. Required for a fulfilled package-tracked input — its product and location are derived from it — and must be omitted for batch/product-tracked and DRAFT inputs.
      - `product_id` string — The input product. Required for product-tracked and DRAFT inputs; for package/batch-tracked inputs it is derived from the package or batch, so omit it.
      - `quantity` number — The consumed quantity in the product's unit. Required for batch-tracked, product-tracked, and DRAFT inputs; omit for package-tracked inputs (use `compliance_quantity` there).
      - `status` 'COMPLETED' | 'DRAFT' | 'PENDING' — PENDING, COMPLETED, or DRAFT. Required when creating. DRAFT reserves product-level inventory without picking a specific lot; PENDING/COMPLETED consume specific inventory.
    - `is_finished_good` boolean — Package-tracked (Metrc) outputs only. When true, Distru flags the created package in Metrc as a Finished Good.
    - `is_trade_sample` boolean — Package-tracked (Metrc) outputs only. Marks the created package as a Metrc trade sample.
    - `location_id` string — The location the output is produced into. Required when creating.
    - `metrc_item_id` integer — Package-tracked (Metrc) outputs only. The Metrc item for the created package. Provide this or `use_same_item=true` (mutually exclusive).
    - `metrc_location_id` integer — Package-tracked (Metrc) outputs only. The Metrc ID of the Metrc location the package will be created in. Only applicable if the output's Metrc license uses Metrc locations.
    - `metrc_notes` string — Package-tracked (Metrc) outputs only. Notes sent to Metrc when the package is created (max 255 characters).
    - `metrc_production_batch_number` string — Package-tracked (Metrc) outputs only. Flags the created package in Metrc as a production batch and gives it this production batch number.
    - `package_date` string — Package-tracked (Metrc) outputs only. The packaged date reported to Metrc for the created package. Defaults to today when omitted.
    - `product_id` string — The product this output produces. Required when creating.
    - `quantity` number — The output quantity in the product's unit. Required when creating product- and batch-tracked outputs. Omit it for package-tracked outputs — send `compliance_quantity` instead and `quantity` is derived from it by unit conversion.
    - `status` 'PENDING' | 'COMPLETED' — PENDING or COMPLETED. Required when creating. On a Metrc processing-job assembly a package-tracked output may be COMPLETED (synced to Metrc) while the assembly stays PENDING; otherwise an output can only be COMPLETED together with the whole assembly.
    - `use_same_item` boolean — Package-tracked (Metrc) outputs only. Reuse the source input package's Metrc item instead of `metrc_item_id` (mutually exclusive with it). Valid only when the output's inputs all share the same Metrc item.
  - `owner_id` string — The ID of the user that owns this assembly. Optional. Editable at any status.
  - `status` 'PENDING' | 'COMPLETED' — The assembly's lifecycle state, `PENDING` or `COMPLETED` (SCREAMING_CASE). Required when creating. `PENDING` claims/reserves ingredient inventory but consumes nothing; `COMPLETED` consumes the inputs and produces the outputs into inventory, and requires every output to be COMPLETED. Creating directly as `COMPLETED` performs that consumption immediately. Once `COMPLETED` an assembly cannot be moved back to `PENDING` and only a few fields remain editable (see the endpoint description).

## Response `200`

The updated assembly

## Other responses

- `201` — The created assembly
- `204` — The assembly was deleted
- `400` — Invalid parameters
- `401` — Missing or invalid API token
- `403` — The API token lacks the required permission
- `404` — Not Found

---

[API](https://skmtc.dev/distru/apis/distru-api.md) · [All operations](https://skmtc.dev/distru/apis/distru-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/distru/distru-api/revisions/eb775b7b6ee0/schema)
