Orders
Orders within a project — the Order object, its products and journal, and every endpoint for listing, creating, updating, deleting and restoring orders.
An order is a deal in a project's sales pipeline. It sits in a status, which belongs to a funnel. It can be assigned to a project member (the responsible user) and linked to a contact. It holds a list of products with their own prices, markups and discounts. Every change is recorded in the order's journal.
Orders are soft-deleted. Deleting an order sets deleted_at and hides it from listings, but its products, journal and
warehouse history are kept, and it can be restored.
Field types follow the field notation.
Access
Every endpoint on this page requires both the projects and orders OAuth scopes. The authenticated user must also
be a member of the project, and the project owner's subscription must be active. Otherwise the endpoint returns
404 RES_NOT_FOUND.
On top of that, the user's project role must grant one of these permissions. The project owner bypasses permission checks.
| Permission | Grants |
|---|---|
can_view_own_orders |
Read orders where the user is the responsible member (user_uuid) |
can_view_role_orders |
Read orders whose responsible member has the same role as the user |
can_view_all_orders |
Read every order in the project |
can_edit_orders |
Create, update, delete and restore orders, and add notes to their journal |
Every endpoint that addresses a single order, including the editing ones, sees only the orders the user's widest view
permission covers. An order outside that range behaves as if it does not exist (404 RES_NOT_FOUND), so editing an
order requires can_edit_orders and visibility of that order.
Order Object
Order Structure
| Field | Type | Description |
|---|---|---|
| uuid | uuid | Order ID |
| serial | string | Human-readable order number, unique within the project: YYDDDNNN, i.e. year, day of the year and that day's sequence, in the owner's time zone |
| status_uuid | ?uuid | Current status. null only if the order's status was deleted. A status can be changed but never cleared through the API |
| user_uuid | ?uuid | Responsible project member |
| user_name | string | Responsible member's full name; empty string if none |
| contact_uuid | ?uuid | Linked contact |
| contact_name | ?string | Linked contact's name |
| loss_reason_uuid | ?uuid | Why the order was lost; set when moving into a canceled-type status |
| loss_reason_name | ?string | Name of the loss reason. Only in Get Order |
| loss_reason_note | ?string | Free-text comment on the loss, up to 512 characters |
| paid_amount | decimal | Amount already paid |
| product_count | integer | Number of product lines |
| total_amount | decimal | Sum of all product lines, see Order Product Structure |
| remaining_amount | decimal | total_amount − paid_amount |
| created_at | ISO8601 datetime | When the order was created |
| updated_at | ISO8601 datetime | When the order was last changed |
| deleted_at | ?ISO8601 datetime | When the order was soft-deleted; null for active orders |
Money amounts are returned as stored, with two decimal places. The project's rounding step (price_step) applies only
to display in the app.
Example Order
{
"uuid": "0199a4e2-7b1c-7c3e-9d2a-5f4e8b1c0a11",
"serial": "26274003",
"status_uuid": "0198f1c0-2d4a-7e11-8b3f-1a2b3c4d5e6f",
"user_uuid": "0198e7aa-91f0-7c22-a4d1-0f9e8d7c6b5a",
"user_name": "Anna Petrova",
"contact_uuid": "0199a4d0-11aa-7b0c-9e8f-7a6b5c4d3e2f",
"contact_name": "Acme Windows LLC",
"loss_reason_uuid": null,
"loss_reason_name": null,
"loss_reason_note": null,
"paid_amount": 500,
"product_count": 2,
"total_amount": 1470,
"remaining_amount": 970,
"created_at": "2026-10-01T09:30:00.000Z",
"updated_at": "2026-10-01T11:02:41.000Z",
"deleted_at": null
}
Order Product Structure
A product line on an order. Read lines with List Order Products. Write them through the
products array of Create Order or Modify Order.
| Field | Type | Description |
|---|---|---|
| product_uuid | ?uuid | The product; null if the product has since been deleted |
| product_name | ?string | Product name |
| configurator_uuid | ?uuid | Configurator the product was built with |
| configuration | ?array | The configurator selections that make up the product |
| unit | ?string | Unit of measure, e.g. pcs |
| purchase_price | decimal | Cost price per unit, calculated server-side from the product's materials when the line is saved |
| sale_price | decimal | Sale price per unit |
| markup_percent | decimal | Markup applied on top of sale_price; can be negative |
| discount_percent | integer | Discount, 0–100 |
| quantity | decimal | Quantity, up to 3 decimal places |
| total_amount | decimal | sale_price × quantity × (1 + markup_percent / 100) × (1 − discount_percent / 100) |
Lines are returned in the order they were sent.
Order Product Input
The shape of each item in the products array of a request body.
| Field | Type | Description |
|---|---|---|
| uuid | uuid | ID of a product in this project |
| sale_price? | decimal | Sale price per unit, ≥ 0. Default 0 |
| markup_percent? | decimal | Markup, may be negative. Default 0 |
| discount_percent? | integer | Discount, 0–100. Default 0 |
| quantity? | decimal | Quantity, ≥ 0. Default 0 |
If the same product uuid appears more than once, the entries are merged into one line. Their quantities are added up,
and the first entry's prices are kept. purchase_price cannot be sent; it is always calculated server-side.
Journal Entry Structure
The journal combines the order's own history with the notes and contact activity linked to it.
| Field | Type | Description |
|---|---|---|
| log_type | string | order for the order's own history, contact for notes and contact activity |
| user_uuid | ?uuid | Who made the change; null for system and automation entries |
| user_name | ?string | Author's full name |
| user_email | ?string | Author's email |
| contact_uuid | ?uuid | Contact the entry is linked to |
| entity_uuid | ?uuid | The order the entry belongs to |
| action | string | What happened, see Journal Actions |
| message | ?string | Note text. Empty string for order entries |
| payload | ?object | Action details. For created/updated, a field-level diff. Entries made by an automation carry payload.automation with its name |
| created_at | ISO8601 datetime | When the entry was recorded |
Journal Actions
| Action | log_type | Meaning |
|---|---|---|
created |
order |
The order was created |
updated |
order |
Fields or products changed; payload holds the diff |
deleted |
order |
The order was soft-deleted |
restored |
order |
The order was restored |
automation_warehouse_movement |
order |
An automation moved stock for this order |
warehouse_return |
order |
Goods from this order were returned to a warehouse |
task_created |
order |
A task linked to this order was created; see Tasks |
task_completed |
order |
A task linked to this order was completed |
note |
contact |
A manual note, see Create Order Note |
email |
contact |
An email was sent by an automation |
Other actions may be added over time. Clients should show unknown actions as generic entries rather than fail.
Example Diff Payload
{
"order": {
"status_name": {
"old": "New",
"new": "In progress"
},
"paid_amount": {
"old": 0,
"new": 500
}
},
"products": {
"added": [
{
"key": "Door",
"changes": {
"sale_price": {
"old": null,
"new": 300
},
"quantity": {
"old": null,
"new": 1
}
},
"data": {
"name": "Door",
"sale_price": 300,
"quantity": 1,
"markup_percent": 0,
"discount_percent": 10,
"purchase_price": 200
}
}
],
"removed": [],
"changed": [
{
"key": "Window",
"changes": {
"quantity": {
"old": 1,
"new": 2
}
},
"fullData": {
"name": "Window",
"sale_price": 100,
"quantity": 2,
"markup_percent": 0,
"discount_percent": 0,
"purchase_price": 60
}
}
]
},
"old_status_uuid": "0198f1c0-2d4a-7e11-8b3f-1a2b3c4d5e6f",
"new_status_uuid": "0198f1c0-2d4a-7e11-8b3f-1a2b3c4d5e70"
}
old_status_uuid / new_status_uuid are present only when the status changed. A created entry carries only
new_status_uuid.
List Orders
GET /api/v1/projects/{project.uuid}/orders
Returns a list of order objects visible to the user, newest first. loss_reason_name is not included.
Requires one of can_view_own_orders, can_view_role_orders, can_view_all_orders.
Query String Params
| Field | Type | Description |
|---|---|---|
| page? | integer | Page number, from 1. Default 1 |
| limit? | integer | Page size, 1–100. Default 50 |
| search? | string | Matches the start of serial if numeric, and any part of the contact name (case-insensitive) |
| statuses? | string | Status UUIDs separated by ; |
| include_unassigned? | 1 |
With statuses, also return orders that have no status |
| members? | string | Responsible member UUIDs separated by ; |
| contacts? | string | Contact UUIDs separated by ; |
| date_from? | ISO8601 datetime | Created at or after |
| date_to? | ISO8601 datetime | Created at or before |
| include_deleted? | 1 |
Also return soft-deleted orders |
Filters combine with AND. The response is a plain array with no total count. Request the next page until a page comes
back shorter than limit.
Example Request
GET /api/v1/projects/0198e7a0-0000-7000-8000-000000000001/orders?statuses=0198f1c0-2d4a-7e11-8b3f-1a2b3c4d5e6f&limit=20
Authorization: Bearer <access_token>
Response
200 OK — an array of order objects.
Errors
| Status | Code | When |
|---|---|---|
| 403 | AUTH_INSUFFICIENT_SCOPE |
The token lacks projects or orders |
| 403 | AUTH_NO_PROJECT_ACCESS |
The user's role has no order view permission |
| 404 | RES_NOT_FOUND |
The project does not exist or is not accessible |
Get Order
GET /api/v1/projects/{project.uuid}/orders/{order.uuid}
Returns a single order, including loss_reason_name. Soft-deleted orders are returned too; check
deleted_at.
Requires one of can_view_own_orders, can_view_role_orders, can_view_all_orders.
Response
200 OK — an order object.
Errors
| Status | Code | When |
|---|---|---|
| 403 | AUTH_INSUFFICIENT_SCOPE |
The token lacks projects or orders |
| 403 | AUTH_NO_PROJECT_ACCESS |
The user's role has no order view permission |
| 404 | RES_NOT_FOUND |
The project or order does not exist, or the order is outside the user's visibility |
Create Order
POST /api/v1/projects/{project.uuid}/orders
Creates an order. The serial is assigned automatically and a created entry is written to the journal. After the
order is saved, the project's order_created automations run; order_status_changed
automations do not run — they run only when the status changes later. A failing order_created automation does not
undo the order; it is recorded in the automation's run history.
Requires can_edit_orders. Passing contact also requires the contacts scope and can_edit_contacts.
JSON Params
| Field | Type | Description |
|---|---|---|
| status_uuid? | uuid | Initial status; must belong to this project. Without it, the status a widget order would get (see Status of new orders) |
| user_uuid? | ?uuid | Responsible member; must be a member of the project |
| contact_uuid? | ?uuid | Contact; must belong to this project |
| contact? | object | The customer, when you have no contact uuid: {name?, email?, phone?, address?} (up to 255/64/32/512 characters). Matched by email or phone like a widget order, otherwise created. Not together with contact_uuid |
| paid_amount? | decimal | Amount already paid, ≥ 0. Default 0 |
| products? | array | Product lines: existing products and configured products in any mix |
Creating an order does not check the status's required fields or loss-reason rule; those apply only when the status changes.
Configured Product Input
A product that does not exist yet: the server builds it from the configurator — name, configuration, bill of materials, cut-list and prices — with the same calculation the widget uses, and reuses an identical existing product if there is one.
| Field | Type | Description |
|---|---|---|
| configurator_uuid | uuid | A configurator of this project |
| values? | object | Control values by control id, as in Calculate; missing controls take their default. A materials control takes {"<material uuid>": {"quantity": 2}} |
| sale_price? | decimal | Sale price per unit, ≥ 0. Default: the calculated sale price |
| markup_percent? | decimal | Markup, may be negative. Default 0 |
| discount_percent? | integer | Discount, 0–100. Default 0 |
| quantity? | decimal | Quantity, ≥ 0. Default 1 |
purchase_price is calculated. Configured lines follow the existing-product lines in the order.
Example Request
{
"status_uuid": "0198f1c0-2d4a-7e11-8b3f-1a2b3c4d5e6f",
"user_uuid": "0198e7aa-91f0-7c22-a4d1-0f9e8d7c6b5a",
"contact_uuid": "0199a4d0-11aa-7b0c-9e8f-7a6b5c4d3e2f",
"paid_amount": 500,
"products": [
{
"uuid": "0199a3f1-5c2e-7d40-b1a2-3c4d5e6f7a8b",
"sale_price": 450,
"quantity": 2,
"discount_percent": 5
},
{
"uuid": "0199a3f1-77d0-7a11-8c9b-0a1b2c4d5e6f",
"sale_price": 615,
"quantity": 1
}
]
}
Example Request: order from a website
{
"contact": {
"name": "John Smith",
"email": "[email protected]",
"phone": "+1 555 010 3000"
},
"products": [
{
"configurator_uuid": "0199ab16-3d4e-7f50-8162-738495061728",
"values": {
"$width": 1200,
"$height": 1600
},
"quantity": 2
},
{
"configurator_uuid": "0199ab16-9a01-7c3e-8d2f-1b2c3d4e5f60",
"values": {
"$length": 3000
}
}
]
}
Response
201 Created
{
"uuid": "0199a4e2-7b1c-7c3e-9d2a-5f4e8b1c0a11"
}
Errors
| Status | Code | When |
|---|---|---|
| 400 | REQ_VALIDATION_FAILED |
The body does not match the schema |
| 400 | ORDER_STATUS_NOT_FOUND |
status_uuid is not a status of this project |
| 400 | ORDER_CONTACT_NOT_FOUND |
contact_uuid is not a contact of this project |
| 400 | ORDER_USER_NOT_MEMBER |
user_uuid is not a member of this project |
| 400 | RES_NOT_FOUND |
A product uuid does not exist in this project |
| 400 | ORDER_PRODUCT_NOT_PRICED |
A configured product cannot be calculated: no such configurator, or an unknown control id in values; details names the item |
| 400 | ORDER_STATUS_NOT_FOUND |
No status_uuid was given and the project has no statuses |
| 403 | AUTH_INSUFFICIENT_SCOPE |
The token lacks projects or orders, or contacts when contact is passed |
| 403 | AUTH_NO_PROJECT_ACCESS |
The user's role lacks can_edit_orders, or can_edit_contacts when contact is passed |
| 404 | RES_NOT_FOUND |
The project does not exist or is not accessible |
Modify Order
PATCH /api/v1/projects/{project.uuid}/orders/{order.uuid}
Updates only the fields you send. If products is sent, it replaces the whole product list; omit it to leave the
lines untouched. Changes are recorded in the journal as an updated entry.
Requires can_edit_orders.
JSON Params
All fields are optional, but at least one must be present.
| Field | Type | Description |
|---|---|---|
| status_uuid? | uuid | New status, see Status changes. Sending null is rejected |
| user_uuid? | ?uuid | Responsible member; null unassigns |
| contact_uuid? | ?uuid | Contact; null unlinks. The order's notes move with it to the new contact |
| paid_amount? | decimal | ≥ 0 |
| products? | array of order product input | Replaces all product lines; [] removes every line |
| loss_reason_uuid? | ?uuid | Loss reason; must belong to this project |
| loss_reason_note? | string | Up to 512 characters |
Status changes
When status_uuid differs from the current status:
- The status cannot be cleared.
nullreturnsORDER_STATUS_REQUIRED. An order whose status was deleted keepsstatus_uuid: nulland can still be edited, as long asstatus_uuidis not sent. - Transition rules. If the target funnel has a transition matrix, the move must be allowed by it. Otherwise the
request fails with
ORDER_STATUS_TRANSITION_NOT_ALLOWED. Moving to a status in a different funnel is always allowed. - Loss reason. Moving into a
canceled-type status requiresloss_reason_uuid, sent with the request or already on the order, whenever the project has at least one loss reason configured. - Required fields. If the target status lists required contact fields, the order's contact (the new one if
contact_uuidis sent) must have all of them filled in. With no contact, every required field counts as missing. - Automations.
order_status_changedautomations run inside the same transaction, after the product list is saved. If any automation fails (for example, a warehouse write-off without enough stock), the whole update is rolled back and the status does not change.
Example Request
{
"status_uuid": "0198f1c0-2d4a-7e11-8b3f-1a2b3c4d5e70",
"paid_amount": 1470
}
Response
204 No Content
Errors
| Status | Code | When |
|---|---|---|
| 400 | REQ_VALIDATION_FAILED |
The body does not match the schema |
| 400 | REQ_NO_DATA_PROVIDED |
The body has no fields |
| 400 | ORDER_STATUS_REQUIRED |
status_uuid is null |
| 400 | ORDER_STATUS_NOT_FOUND |
status_uuid is not a status of this project |
| 400 | ORDER_STATUS_TRANSITION_NOT_ALLOWED |
The funnel's transition matrix forbids this move |
| 400 | ORDER_LOSS_REASON_REQUIRED |
Moving into a canceled status without a loss reason |
| 400 | ORDER_REQUIRED_FIELD_MISSING |
The contact is missing fields the status requires; the response lists them in fields |
| 400 | ORDER_CONTACT_NOT_FOUND |
contact_uuid is not a contact of this project |
| 400 | ORDER_USER_NOT_MEMBER |
user_uuid is not a member of this project |
| 400 | ORDER_LOSS_REASON_NOT_FOUND |
loss_reason_uuid is not a loss reason of this project |
| 400 | RES_NOT_FOUND |
A product uuid does not exist in this project |
| 403 | AUTH_INSUFFICIENT_SCOPE |
The token lacks projects or orders |
| 403 | AUTH_NO_PROJECT_ACCESS |
The user's role lacks can_edit_orders |
| 404 | RES_NOT_FOUND |
The project or order does not exist, or the order is outside the user's visibility |
| 409 | ORDER_DELETED |
The order is soft-deleted; restore it first |
| 409 | ORDER_INSUFFICIENT_STOCK |
A status-change automation could not write off stock; see below |
Example Error: Validation
REQ_VALIDATION_FAILED lists every problem in errors. path points to the offending field.
{
"id": 2001,
"code": "REQ_VALIDATION_FAILED",
"message": "The request was improperly formatted or contained invalid data.",
"errors": [
{
"origin": "number",
"code": "too_big",
"maximum": 100,
"inclusive": true,
"path": [
"products",
0,
"discount_percent"
],
"message": "Too big: expected number to be <=100"
}
]
}
Example Error: Required Fields
{
"id": 4007,
"code": "ORDER_REQUIRED_FIELD_MISSING",
"message": "One or more fields required for this status are not filled in.",
"fields": [
"inn",
"delivery_address"
]
}
Example Error: Insufficient Stock
{
"id": 4005,
"code": "ORDER_INSUFFICIENT_STOCK",
"message": "Not enough stock to complete this operation.",
"warehouse_uuid": "0199a0b1-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
"shortages": [
{
"material_uuid": "0199a0c2-1111-7222-8333-444455556666",
"item_type": "material",
"name": "Aluminium profile 60mm",
"unit": "m",
"needed": 12.5,
"available": 8
}
]
}
Replace Order
PUT /api/v1/projects/{project.uuid}/orders/{order.uuid}
Same as Modify Order, except that the full object is required.
Requires can_edit_orders.
JSON Params
| Field | Type | Description |
|---|---|---|
| status_uuid | ?uuid | Must be sent. null is accepted only if the order already has no status |
| user_uuid | ?uuid | Must be sent; null unassigns |
| contact_uuid | ?uuid | Must be sent; null unlinks |
| paid_amount | decimal | ≥ 0 |
| products? | array of order product input | Replaces all lines. Omitting it removes every line |
| loss_reason_uuid? | ?uuid | Loss reason |
| loss_reason_note? | string | Up to 512 characters |
The status change rules apply in the same way.
Response
204 No Content
Errors
Same as Modify Order, except REQ_NO_DATA_PROVIDED.
Delete Order
DELETE /api/v1/projects/{project.uuid}/orders/{order.uuid}
Soft-deletes the order: sets deleted_at and removes it from List Orders unless
include_deleted=1 is passed. Products, journal and warehouse history are kept. A deleted entry is added to the
journal.
Requires can_edit_orders.
Response
204 No Content
Errors
| Status | Code | When |
|---|---|---|
| 403 | AUTH_INSUFFICIENT_SCOPE |
The token lacks projects or orders |
| 403 | AUTH_NO_PROJECT_ACCESS |
The user's role lacks can_edit_orders |
| 404 | RES_NOT_FOUND |
The project or order does not exist, is already deleted, or is outside the user's visibility |
Restore Order
POST /api/v1/projects/{project.uuid}/orders/{order.uuid}/restore
Restores a soft-deleted order by clearing deleted_at. A restored entry is added to the journal.
Requires can_edit_orders.
Response
204 No Content
Errors
| Status | Code | When |
|---|---|---|
| 403 | AUTH_INSUFFICIENT_SCOPE |
The token lacks projects or orders |
| 403 | AUTH_NO_PROJECT_ACCESS |
The user's role lacks can_edit_orders |
| 404 | RES_NOT_FOUND |
The project or order does not exist, the order is not deleted, or it is outside the user's visibility |
List Order Products
GET /api/v1/projects/{project.uuid}/orders/{order.uuid}/products
Returns the order's product lines as an array of order product objects.
Requires one of can_view_own_orders, can_view_role_orders, can_view_all_orders.
Example Response
[
{
"product_uuid": "0199a3f1-5c2e-7d40-b1a2-3c4d5e6f7a8b",
"product_name": "PVC window 1200×1400",
"configurator_uuid": "0199a3e0-0a0b-7c0d-8e0f-101112131415",
"configuration": [],
"unit": "pcs",
"purchase_price": 310,
"sale_price": 450,
"markup_percent": 0,
"discount_percent": 5,
"quantity": 2,
"total_amount": 855
}
]
Errors
| Status | Code | When |
|---|---|---|
| 403 | AUTH_INSUFFICIENT_SCOPE |
The token lacks projects or orders |
| 403 | AUTH_NO_PROJECT_ACCESS |
The user's role has no order view permission |
| 404 | RES_NOT_FOUND |
The project or order does not exist, or the order is outside the user's visibility |
Get Order Journal
GET /api/v1/projects/{project.uuid}/orders/{order.uuid}/logs
Returns up to 50 journal entries, oldest first. The list includes the order's own history
and any notes on the order. With contact_uuid, it also includes that contact's whole activity history.
Requires one of can_view_own_orders, can_view_role_orders, can_view_all_orders.
Query String Params
| Field | Type | Description |
|---|---|---|
| contact_uuid? | uuid | Also include this contact's activity |
| last_log? | ISO8601 datetime | Return only entries created strictly after this time, for incremental polling |
To poll for new entries, pass the created_at of the last entry you have as last_log.
Response
200 OK — an array of journal entry objects.
Errors
| Status | Code | When |
|---|---|---|
| 403 | AUTH_INSUFFICIENT_SCOPE |
The token lacks projects or orders |
| 403 | AUTH_NO_PROJECT_ACCESS |
The user's role has no order view permission |
| 404 | RES_NOT_FOUND |
The project or order does not exist, or the order is outside the user's visibility |
Create Order Note
POST /api/v1/projects/{project.uuid}/orders/{order.uuid}/logs
Adds a note to the order's journal. The order does not need a contact. If a contact is linked later, the note moves to that contact's history automatically.
Requires can_edit_orders, and the order must be within the user's visibility.
JSON Params
| Field | Type | Description |
|---|---|---|
| message | string | Note text, 1–5000 characters (whitespace trimmed) |
Example Request
{
"message": "Customer asked to move delivery to next Friday."
}
Response
201 Created with an empty body.
Errors
| Status | Code | When |
|---|---|---|
| 400 | REQ_VALIDATION_FAILED |
message is missing, empty or too long |
| 403 | AUTH_INSUFFICIENT_SCOPE |
The token lacks projects or orders |
| 403 | AUTH_NO_PROJECT_ACCESS |
The user's role lacks can_edit_orders |
| 404 | RES_NOT_FOUND |
The project or order does not exist, the order is deleted, or it is outside the user's visibility |