---
title: "Upsert a company"
method: POST
path: "/public/v1/companies"
tags: ["Company"]
---

# Upsert a company

`POST /public/v1/companies`

Create or update one company in your CRM through a single endpoint. Omit `id` to create; pass an existing company `id` to update that entry. The response is the same object returned by `GET /public/v1/companies/{id}`, with status 201 on create and 200 on update.

A company here is your relationship with another business plus the underlying business record. The two are written together: `name`, `category`, `legal_business_name`, `default_email`, `phone_number`, `website`, `default_sales_order_notes`, and `default_purchase_order_notes` describe the business itself, while the emails, `owner_id`, `group_id`, `relationship_type_id`, `default_payment_term_id`, `outstanding_balance_threshold`, and `custom_data` describe your relationship with it. On create both records are created in one call; on update both are amended in place — no second company is created.

Updates are sparse: only the fields you send change, and any field you omit keeps its current value. Sending `custom_data`, however, replaces the entire custom-field map — any custom field not present in the map you send is cleared, so send the full set you want to keep. Required custom fields are enforced on both create and update, so a create or update that leaves a required custom field unset is rejected. Renaming a company also updates its public menu URLs, so previously shared menu links for that company change.

This endpoint does not touch inventory and has no state-traceability (Metrc/BioTrack) effect — companies are CRM records only. A few response fields are read-only and cannot be set here: the computed `outstanding_balance`, `leaflink_brand_id`, and the nested `licenses` and `locations` — manage a company's licenses with [POST /public/v1/companies/{id}/licenses](#upsert-a-companys-license) and its locations with [POST /public/v1/companies/{id}/locations](#upsert-a-companys-location) instead.

You can link this company to your QuickBooks Online and LeafLink records through `qb_customer_id`, `qb_vendor_id`, and `leaflink_customer_id`. These run the same checks and side effects as the in-app mapping screens: the id must exist in your synced QuickBooks Online / LeafLink data, each may be linked to only one company on your account, and linking a `leaflink_customer_id` reassigns that customer's existing LeafLink orders to this company. Send an explicit `null` to unlink; omit the field to leave the current link unchanged. The link runs in the same transaction as the rest of the upsert, so if it is rejected the whole request is rolled back and nothing is persisted.

Validation is all-or-nothing: if any field is rejected the whole upsert fails and nothing is persisted; errors come back as a `400` with an `errors` array. Referencing an `id` that does not exist on your account (or that your team restrictions hide) returns `404`.

Required permission: `companies_permissions_create` to create; `companies_permissions_edit`, plus access to the company under team restrictions, to update. Setting `qb_customer_id` or `qb_vendor_id` additionally requires `settings_permissions_quickbooks`; setting `leaflink_customer_id` additionally requires `companies_permissions_update_leaflink_data`.

## Request body

- object
  - `id` string — ID of the company to update. When present, that company is updated and its business record amended in place; when absent, a new company and its underlying business record are created together. Must reference a company on your account that you can access under team restrictions, otherwise the request returns 404.
  - `name` string — Display name of the company. Required on create; left as-is when omitted on update. Must be at least one character and cannot contain special characters. Must be unique among the active companies on your account, on both create and update — a duplicate name is rejected. Renaming an existing company also rewrites its public menu URLs, so previously shared menu links for that company change.
  - `category` string — Business category for the company. Must be one of a fixed set of values, returned verbatim in the response's `category` field (note these are Title Case, not the SCREAMING_CASE used by other enums in this API): `Dispensary`, `Delivery`, `Cultivator`, `Manufacturer`, `Distributor`, `Microbusiness`, `Lab`, `Retail`, or `Other`. Any other value is rejected. Optional; left as-is when omitted on update.
  - `legal_business_name` string — The company's registered legal business name, distinct from its display `name`. Left as-is when omitted on update.
  - `default_email` string — Primary email address for the company. Must be a valid email address when provided. Left as-is when omitted on update.
  - `phone_number` string — Phone number for the company. Free-text; not format-validated. Left as-is when omitted on update.
  - `website` string — Website URL for the company. Left as-is when omitted on update.
  - `default_sales_order_notes` string — Notes pre-filled onto new sales orders created for this company. Left as-is when omitted on update.
  - `default_purchase_order_notes` string — Notes pre-filled onto new purchase orders created for this company. Left as-is when omitted on update.
  - `invoice_email` string — Email address that invoices for this company are sent to. Left as-is when omitted on update.
  - `sales_order_email` string — Email address that sales orders for this company are sent to. Left as-is when omitted on update.
  - `purchase_order_email` string — Email address that purchase orders for this company are sent to. Left as-is when omitted on update.
  - `order_shipment_email` string — Email address that order shipment notifications for this company are sent to. Left as-is when omitted on update.
  - `relationship_type_id` string — ID of the relationship type to assign to this company (surfaces as the `relationship_type` object in responses). Left as-is when omitted on update.
  - `group_id` string — ID of the group to assign to this company (surfaces as the `group` object in responses). Left as-is when omitted on update.
  - `owner_id` string — ID of the user who owns this company. Must be a user visible to the authenticated user under their team restrictions, otherwise the request is rejected. Left as-is when omitted on update.
  - `default_payment_term_id` string — ID of the payment term applied by default to this company. Use `GET /public/v1/payment-terms` to look up available payment term IDs. Left as-is when omitted on update.
  - `outstanding_balance_threshold` integer — Outstanding-balance (unpaid invoice total) at which Distru starts showing warnings and sending alerts for this company. A positive integer in your account's currency major unit (e.g. whole dollars), compared directly against the company's outstanding balance. When null, the account-wide default threshold applies; when set, it supersedes that default. Left as-is when omitted on update.
  - `qb_customer_id` string — Links this company to a QuickBooks Online customer. Must be a customer id present in your synced QuickBooks Online data, and each customer may be linked to only one company on your account. Left as-is when omitted; send null to unlink. Requires the `settings_permissions_quickbooks` permission.
  - `qb_vendor_id` string — Links this company to a QuickBooks Online vendor. Must be a vendor id present in your synced QuickBooks Online data, and each vendor may be linked to only one company on your account. Left as-is when omitted; send null to unlink. Requires the `settings_permissions_quickbooks` permission.
  - `leaflink_customer_id` integer — Links this company to a LeafLink customer. Must be a customer id present in your synced LeafLink data, and each customer may be linked to only one company on your account. Linking also reassigns that customer's existing LeafLink orders to this company. Cannot be set on a company that represents your own business. Left as-is when omitted; send null to unlink (existing orders keep their current company). Requires the `companies_permissions_update_leaflink_data` permission.
  - `custom_data` object — A map of custom field IDs to their values. Use `GET /public/v1/custom-fields?parent_object=company` to retrieve available custom 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. This replaces the whole custom-field map — any field you omit from the map is cleared, so send every value you want to keep. Fields configured as required must be present with a value or the request is rejected.

## Response `200`

An updated company relationship

## Other responses

- `201` — A new company relationship
- `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/f8930b2c2924/schema)
