> For the complete documentation index, see [llms.txt](https://docs.flowretail.com/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.flowretail.com/docs/developers/api-documentation/service/create-new-service-order-event.md).

# Create new service-order event

Write a new entry on the service order timeline: a status change, a note, a diagnosis, a resolution or a message from the store to the customer. Only those five event types are accepted here — every other type is written by Flow when the corresponding action happens. A status change that moves the service order out of a terminal state is recorded and returned as REOPENED rather than as STATUS\_CHANGED. A message to the customer is a STORE\_MESSAGE: the store's half of the dialogue in the service portal, beside the customer's own CUSTOMER\_MESSAGE. It is always PUBLIC — a STORE\_MESSAGE with any other visibility is refused with a 400 and writes nothing at all, whether or not it names a channel. It carries no status, as no entry but a status change does. A STORE\_MESSAGE whose note is empty or holds nothing but whitespace is refused with a 400 and writes nothing at all. It is never printed on any paper. Named with a channel it is sent to the customer as well as shown in the portal; named without one it is shown in the portal and nothing is sent. Customer channels are accepted on a STORE\_MESSAGE alone. A channel named on a STATUS\_CHANGED, a status change that is recorded as REOPENED included, or on a NOTE, a DIAGNOSIS or a RESOLUTION, is refused with a 400 and writes nothing at all, whatever its visibility. A status change messages the customer only through the status rule on the status it moves to, in the text the tenant wrote for that rule and with no note, so one status change never sends the customer two messages. Those three are the written entries, and their visibility says only where they are shown: INTERNAL on the case alone, REPORT on the papers alone, PUBLIC in the portal and on the papers. The papers carry written entries only, so a STORE\_MESSAGE is never on one. While the tenant setting termsAcceptanceRequired is on, every status change into the state IN\_PROGRESS on a service order that has terms is refused until a standing acceptance exists on it — not only the first. A service order already in work is never un-started by a void, but once it leaves IN\_PROGRESS it does not come back in until the terms are accepted afresh. However the acceptance is given — in the service portal, on a counter device or in the store — it has to stand before the work can start, and an acceptance that has been voided no longer satisfies anything. A service order without terms is never held, and while the setting is off nothing waits on the terms. Creating a STATUS\_CHANGED event with the status the service order already has is a no-op. No event is written, no status rule fires and no message goes out, and the answer is 200 with the service order unchanged. Sending the customer the same message again on purpose is what the send endpoint is for. The call is idempotent on serviceEventUid: generate the uid before the first attempt and reuse it on every retry. A retry of a request that did land falls into the same no-op — it is answered 200 with the service order unchanged, and nothing is written a second time. A double tap on the same status is answered the same way. An event is immutable once written apart from its note and its visibility, which are edited through the update endpoint. Nothing else about it can be changed afterwards. At the store the service order was taken in at, a NOTE is the one entry that may be added, and every other entry type, a STORE\_MESSAGE included, is refused there with a 403; at a store that neither owns the service order nor took it in, every entry, a NOTE included, is refused with a 403. A STORE\_MESSAGE is therefore written at the owning store alone. Editing or deleting an entry belongs to the owning store. The request's own refusals come first, answered before the store is judged — among them an event type outside the five, an unknown visibility, a serviceStatusUid naming no status of the tenant and a malformed serviceEventUid. The store is judged next, and only then what the entry would change: at the store the service order was taken in at, an entry that is not a NOTE is refused with a 403, and at a store that neither owns the service order nor took it in every entry is, even when recording it there would have changed nothing. The messages switch is judged there too: while the tenant's service messages are paused, an entry naming a channel is refused even when the entry itself would have changed nothing. The entry-type and visibility rules are judged in the same place: a channel named on any type but a STORE\_MESSAGE — a status change to the status the service order already has included — and a STORE\_MESSAGE with any visibility but PUBLIC are refused even when the entry itself would have changed nothing. The text behind the channel is judged there too: a channel the tenant has written no text for is refused even when the entry itself would have changed nothing.

Required permissions: SERVICE\_ORDER

```json
{"openapi":"3.0.2","info":{"title":"Flow Retail API version 2","version":"2.0.0"},"tags":[{"name":"Service","description":"Service"}],"servers":[{"description":"Flow Retail Production","url":"https://api.flowretail.com/v2"},{"description":"Flow Retail Stage","url":"https://api.stage.flowretail.com/v2"}],"security":[{"accessToken":[]},{"authToken":[]},{"integrationToken":[]}],"components":{"securitySchemes":{"accessToken":{"description":"Access token recevied after user login with a deviceToken","scheme":"bearer","type":"http"},"authToken":{"description":"Token received from the auth service","scheme":"bearer","type":"http"},"integrationToken":{"scheme":"bearer","type":"http"}},"responses":{"ServiceOrderResponse":{"description":"Example response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceOrderResponseSchema"}}}},"ServiceEventResponse":{"description":"Example response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceEventResponseSchema"}}}}},"schemas":{"ServiceOrderResponseSchema":{"title":"ServiceOrderResponseSchema","description":"A service order as staff read it, with everything on the case: what came in, who is on it, what it will cost, the intake form values, the secrets by label, the job types the work consists of and the answers given on their checklists.\nHow much of that is returned depends on where the service order is read from. This full representation is what the single service order endpoint returns, secret labels and sensitive form values included, at the store that owns the service order and at the store it was taken in at. Read at any other store of the tenant, two things are held back: secrets is an empty array, and formValues leaves out the values captured for sensitive fields. In the results of a service order search or of the store's service order list, three things are held back: secrets and checklistAnswers are always empty arrays, and formValues leaves out the values captured for sensitive fields. Read the one service order to see them: the checklist answers at any store of the tenant, the secrets and the sensitive values at the store that owns it or the store it was taken in at. It is a staff representation throughout, and the two customer-facing surfaces do not use it at all: the service portal has PortalServiceOrderResponseSchema and the customer data export has CustomerExportServiceOrderSchema, each assembled from customer-visible data of its own.","type":"object","required":["serviceOrderUid","tenantUid","storeUid","orderProductLineUid","orderUid","customerUid","customerName","createdFromStoreUid","assignedUserUid","assignedUserDisplayName","serviceTypeUid","serviceTypeName","serviceStatusUid","serviceStatusName","serviceState","title","serviceNumber","externalServiceNumber","externalSalesOrderNumber","description","priority","estimatedPrice","maxPrice","readyAtEstimate","createdAt","createdBy","lastModifiedAt","lastModifiedBy","totals","items","assets","rmaOrderItemUid","rmaOrderUid","formValues","intakeFormUid","secrets","serviceJobTypes","checklistAnswers","quotedPrice","quoteState","termsText","termsUrl","termsAccepted","coverage","productUid","insuranceCompanyCustomerUid","deductible","claimNumber"],"properties":{"serviceOrderUid":{"$ref":"#/components/schemas/UID"},"tenantUid":{"$ref":"#/components/schemas/UID"},"storeUid":{"description":"The store that owns the service order — the store the work sits with and where the customer collects it. It changes only through the move mechanism — the move endpoint, or the receipt of a shipment item at another store — which writes a MOVED event when it does.","type":"string","pattern":"^[A-Za-z0-9-_]+"},"orderProductLineUid":{"description":"The line of the sales order the unit was bought on, which is what ties the case to the purchase behind it. An empty string when the case names no sale.\nIt is set when the service order is registered and changed or cleared afterwards through the update. A case that names a line is what makes the sales order list it under the relation SOLD_ITEM.","allOf":[{"$ref":"#/components/schemas/OptionalUID"}]},"orderUid":{"description":"The sales order the linked line belongs to. An empty string when the case names no sale.\nIt is derived from orderProductLineUid and never sent by a client: there is no order field on the create or the update, and clearing the line clears this with it, so the two always name the same sale.","allOf":[{"$ref":"#/components/schemas/OptionalUID"}]},"customerUid":{"$ref":"#/components/schemas/OptionalUID"},"customerName":{"type":"string"},"createdFromStoreUid":{"description":"The store the service order was taken in at. It is set when the service order is registered and never changes afterwards, so it keeps recording where the unit was handed in however far the case travels. storeUid is the store that owns the service order now — where the work sits and where the customer collects it — so the two differ for the rest of the case once a service order has been moved between stores.","type":"string","pattern":"^[A-Za-z0-9-_]+"},"assignedUserUid":{"description":"The user who is to do the work on this service order. An empty string means no personal assignee: the service order is held by the store that owns it, and its notifications go to that store's service staff rather than to one person, so nothing is left waiting on somebody who is not looking at it.\nAssigning and unassigning happen on the service order's assign endpoint, which writes an ASSIGNED event either way. A move to another store also ends the assignment: the service order arrives at the receiving store unassigned, recorded on the MOVED event.","type":"string","pattern":"^[A-Za-z0-9-_]*"},"assignedUserDisplayName":{"type":"string"},"serviceTypeUid":{"$ref":"#/components/schemas/UID"},"serviceTypeName":{"type":"string"},"serviceStatusUid":{"$ref":"#/components/schemas/UID"},"serviceStatusName":{"type":"string"},"serviceState":{"$ref":"#/components/schemas/ServiceStateENUM"},"title":{"type":"string"},"serviceNumber":{"type":"string"},"externalServiceNumber":{"type":"string","description":"The number an outside workshop or supplier handling the work gives the case in its own system, such as the supplier's case number. It is never serviceNumber, and never the number of a sale: the sales order the unit was bought on is externalSalesOrderNumber."},"externalSalesOrderNumber":{"type":"string","description":"The order number of the sales order the unit in for service was bought on, in the tenant's external order system such as Omnium. It is the sale's number, never serviceNumber and never externalServiceNumber, and it is text alone: the sale itself is looked up in the external system. An empty string when the service order names no external sale."},"description":{"type":"string"},"locationLabel":{"type":"string","deprecated":true,"description":"Deprecated. Superseded by the intake form field of system kind LOCATION_LABEL: where the device is stored while it is in the workshop is captured as a form value, and is read from formValues. Until removal it is served as a derived read-only alias of the value captured for that field, and it is an empty string when no value is captured for it. It will be removed after the announced deprecation window; see the deprecation log for the date."},"priority":{"description":"How urgent the work is. An intake form field of system kind PRIORITY writes here rather than into the form values. NONE is no priority.","allOf":[{"$ref":"#/components/schemas/PriorityENUM"}]},"requiresPriceEstimate":{"type":"boolean","deprecated":true,"description":"Deprecated. Asking the customer what the work will cost before it is started is the quote: quotedPrice is the offer that was made and quoteState is how the customer answered it, both written through the quote endpoints, which record the offer and the answer rather than a flag saying one is owed. During the deprecation window it is served from the stored value on service orders registered before the change, and is false on every service order registered after it; it is no longer in the required set. It will be removed after the announced deprecation window; see the deprecation log for the date."},"estimatedPrice":{"type":"integer","description":"What the work is estimated to cost. It is captured at intake when the intake form places a field of system kind ESTIMATED_PRICE, which writes here rather than into the form values. It is 0 when no estimate has been captured."},"maxPrice":{"type":"integer","description":"The ceiling the customer allows the work to be done up to without being asked again. It is captured at intake when the intake form places a field of system kind MAX_PRICE, which writes here rather than into the form values. It is 0 when no ceiling has been captured."},"readyAtEstimate":{"type":"string","format":"date-time","nullable":true,"description":"When the service order is estimated to be finished. null when no date has been given.\nThe value is read on the clock in the tenant's timezone. A value that reads 23:59:59 there, whatever its milliseconds, means ready by the end of that day with no time picked, and is shown as the day and \"end of day\". Any other value is a picked time and is shown as the day and that time. Clients send a picked time in whole minutes, so its seconds read :00, and a picked 00:00 or 23:59 is never mistaken for the end of the day.\nA writer with no picked time sends 23:59:59 on the chosen day in the tenant's timezone, which is the value the service type's per-store turnaround response prefills.\nThe tenant's timezone is the tenant's zone name — timezone on the tenant, in tz/zoneinfo format such as Europe/Oslo, which the service portal page carries as timeZone — and never a fixed offset, so a day the clocks change on reads right. The zone is Europe/Oslo when the tenant has none set. No store carries a zone of its own.\nThe texts core writes follow the same rule: the .readyDate variable in SMS and e-mail texts, the service papers that print the expected ready time, the intake slip and the note of a FIELD_CHANGE entry for readyAtEstimate give the day and the time, or the day and \"end of day\", in the language the text is written in."},"readyAtNeeded":{"type":"string","format":"date-time","nullable":true,"deprecated":true,"description":"Deprecated. The service module follows one date, readyAtEstimate; a deadline the workshop cannot control is not tracked. During the deprecation window it is served from the stored value on service orders registered before the change, and is null on every service order registered after it; it is no longer in the required set. It will be removed after the announced deprecation window; see the deprecation log for the date."},"publicPageHash":{"type":"string","deprecated":true,"description":"Deprecated. Staff surfaces never need the customer's link: sending it, sending it again and replacing it all happen server-side, through the send endpoint and the portal token endpoint, and signing at the counter has its own signing session that reaches the terms of that one service order and nothing else. Handing the customer's portal credential to every staff client is what made it leak, and there is no longer a reason to.\nIt is served as an empty string for the whole deprecation window, and is no longer in the required set: the field stays in the response so a client that requires a string still parses, and it carries nothing. There is no value to read here — a service order's portal link reaches the customer by being sent, and by nothing else. It will be removed after the announced deprecation window; see the deprecation log for the date."},"warranty":{"type":"boolean","deprecated":true,"description":"Deprecated. Superseded by coverage, which classifies who pays for the work and under what right: warranty true is the coverage WARRANTY_CLAIM. Until removal it is served as a derived read-only alias — computed from the coverage on every read, true when the coverage is WARRANTY_CLAIM and false otherwise. It is read-only in the strict sense: the service order's write side carries no warranty at all, and the coverage is set through coverage alone. A warranty property sent on a create or an update is not a value the request carries — it changes nothing and is not refused — so a client that reads a service order and writes the body back unaltered sets no coverage by doing it. It will be removed after the announced deprecation window; see the deprecation log for the date."},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"lastModifiedAt":{"type":"string","format":"date-time"},"lastModifiedBy":{"type":"string"},"totals":{"$ref":"#/components/schemas/ServiceOrderTotalsSchema"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ServiceOrderItemResponseSchema"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/ServiceOrderAssetResponse"}},"rmaOrderItemUid":{"$ref":"#/components/schemas/OptionalUID"},"rmaOrderUid":{"$ref":"#/components/schemas/OptionalUID"},"formValues":{"type":"array","description":"The values captured on the intake form the service order was created from, each with the field information as it was at capture time. Empty when no values were captured. An answer stays listed when a later edit of the form hides its placement or takes the field off the form, so a client that renders the form judges visibleWhen itself. Read at a store that neither owns the service order nor took it in, the values captured for sensitive fields are left out.\nA SECRET field never has a form value: what was captured for it is a secret, listed under secrets. A sensitive value that was removed because the service order reached a terminal state is still listed, with redacted true and an empty value — the question stays on the record, the answer does not.","items":{"$ref":"#/components/schemas/ServiceOrderFormValueResponseSchema"}},"intakeFormUid":{"description":"The intake form the service order was created from. It names the form rather than copying it: archiving the form afterwards leaves this uid as it is, and an edit of the form applies to the service orders created from it as well. Form values are checked against this form as it reads at the moment of the write, and an update is checked only for what it changes (formValues on the update), so a question an edit adds or makes required is asked of an existing service order only when an update clears it or sends an answer that turns it from hidden to shown.\nAn empty string when the service order was created without an intake form.","type":"string","pattern":"^[A-Za-z0-9-_]*"},"secrets":{"type":"array","description":"The secrets kept on the service order, such as a device passcode, by label. Empty when the service order has no secrets.\nThe values are never returned here — read one through the secrets reveal endpoint. Every secret is deleted when the service order reaches a terminal state, so this list is empty on a finished service order. It is empty too when the service order is read at a store that neither owns it nor took it in.","items":{"$ref":"#/components/schemas/ServiceOrderSecretResponseSchema"}},"serviceJobTypes":{"type":"array","description":"The job types the work on this service order consists of, usually one, with their names embedded so the service order can be shown without looking them up. Empty when no job type has been set.\nEach job type carries the checklist run before handover and the products suggested when the work is priced — read those through the job types endpoint.","items":{"$ref":"#/components/schemas/ServiceJobTypeRefSchema"}},"checklistAnswers":{"type":"array","description":"The answers given on the checklists of the job types the service order carries, one entry per answered item, for the items on those checklists as they read now. An item with no answer has no entry: a cleared answer is simply absent. Answers are given one at a time during the work through put-serviceorders-checklist-answers, and the results post-serviceorders-checklist sends are stored as answers too.\nAn answer belongs to the item's text, so an item renamed on the job type reads as unanswered. Answers on a job type taken off the service order are kept but not listed, and are listed again, with who gave them and when, if the job type is put back.\nIt is staff data and internal: it is never on the service portal page, the service papers, the customer data export or a webhook payload, so staff names never reach a customer. Every response that carries one service order carries it, at any store of the tenant — the single read, the read by number, and every write that returns the service order, put-serviceorders-checklist-answers included; in a list of service orders — a search or the store's service order list — it is an empty array.","items":{"$ref":"#/components/schemas/ServiceOrderChecklistAnswerSchema"}},"quotedPrice":{"type":"integer","nullable":true,"description":"The amount of the latest quote on the service order, in the same unit as estimatedPrice and maxPrice. Where that offer stands is quoteState's job rather than this field's: null here means nothing has ever been offered.\nIt is set only through the service order's quote endpoint, never through an ordinary update, so that offering a price always leaves a QUOTE_SENT event with the amount snapshotted. The customer's answer does not clear it: the amount stays as it is after a decision, approved or declined, until a new quote replaces it. estimatedPrice and maxPrice are separate figures and are not touched by quoting."},"quoteState":{"description":"Where the latest quote stands: NONE until a price has been offered, SENT while an offer is waiting for an answer, and APPROVED or DECLINED once the customer has given one, wherever they gave it.\nIt moves only through the quote endpoints, and it is what the answer is judged against — a decision is refused unless it is SENT, so after an answer a new quote has to be sent before another one can be recorded. It is the same state the service portal shows the customer.","allOf":[{"$ref":"#/components/schemas/ServiceOrderQuoteStateENUM"}]},"termsText":{"type":"string","description":"The terms and conditions that apply to this service order, in the terms format that termsText in the service settings describes: the tenant's terms followed by the additional terms of the service order's service type, the two separated by a blank line, each as it read when the service order was created. Editing the tenant's terms or the type's additional terms afterwards never changes it — a service order keeps the wording that was in force when it was taken in, so the printed confirmation says the same thing years later.\nChanging serviceTypeUid while no standing acceptance exists on the service order replaces the type's part with the new type's additional terms as in force at the change; once an acceptance exists, the text is frozen. An empty string when neither the tenant nor the type had any terms when the service order was created — service orders migrated from the old service system and orders created before any terms were saved among them — then, unless termsUrl is set, nothing is shown to the customer, no acceptance can be recorded, and termsAccepted is true."},"termsUrl":{"type":"string","description":"Where the tenant publishes their terms, as the URL that was in force when the service order was created — for tenants who keep the wording on their own website. Shown to the customer alongside termsText and printed on the service confirmation and the intake slip. An empty string when the tenant had no URL then. It counts as terms: a service order with a URL and no text has terms to accept."},"termsAccepted":{"type":"boolean","description":"Whether the terms that apply to this service order have been accepted. It is derived, never written: true when a standing acceptance exists on the service order, whichever way it was given — in the service portal, signed on a counter device, or given in the store and recorded by staff — and true when the service order has no terms at all, termsText and termsUrl both empty, because then nothing is asked. An acceptance that has been voided satisfies nothing, so voiding the one that stood puts this back to false.\nIt says whether the terms have been accepted, not whether acceptance is required: the tenant setting termsAcceptanceRequired decides whether a status change may move an unaccepted service order into IN_PROGRESS. Read the acceptance itself, with the terms as they read when accepted and the drawn signature where there is one, through the service order's terms acceptances endpoint."},"coverage":{"description":"Who pays for the work on this service order, and under what right. It is chosen at intake and can change while the case runs: a diagnosis may reclassify it — water damage voiding a warranty makes the case payable — which is an ordinary logged field edit and leaves a FIELD_CHANGE event on the timeline.\nIt replaces the deprecated warranty flag: the coverage WARRANTY_CLAIM is what warranty true used to mean.","allOf":[{"$ref":"#/components/schemas/ServiceOrderCoverageENUM"}]},"productUid":{"description":"The product the unit in for service is — the model of the machine on the bench, so the case can be found by what came in and the serial can be read against the right product. It is captured at intake when the intake form places a field of system kind PRODUCT, which writes here rather than into the form values.\nThis is not one of the products on the service order's items: those are the parts and the work that go on the bill. An empty string when the unit was not identified as a catalogue product.","type":"string","pattern":"^[A-Za-z0-9-_]*"},"insuranceCompanyCustomerUid":{"description":"The insurance company the claim is made to, as a customer record of its own. It is recorded for information: the sale is billed to the service order's customer whatever the coverage (see post-serviceorders-orders).\nSet it when the coverage is INSURANCE_CLAIM; an empty string otherwise. The number the insurer gave the claim is claimNumber.","type":"string","pattern":"^[A-Za-z0-9-_]*"},"deductible":{"type":"integer","description":"The deductible on an insurance claim, VAT included, in the same unit as estimatedPrice and maxPrice, recorded for information and printed on the service papers; it does not change how the case is billed. It is 0 when there is none, and it is above 0 only on the coverage INSURANCE_CLAIM."},"claimNumber":{"type":"string","description":"The number the insurer gave the claim, which the insurer matches the bill and the damage report to. It is entered beside the insurer and the deductible and is not an intake form field. An empty string when none was entered, and it is non-empty only on the coverage INSURANCE_CLAIM."}}},"UID":{"title":"UID","type":"string","pattern":"^[A-Za-z0-9-_]+"},"OptionalUID":{"title":"OptionalUID","type":"string","pattern":"^[A-Za-z0-9-_]*"},"ServiceStateENUM":{"title":"ServiceStateENUM","description":"Where a service order stands, as the platform understands it. Every status a tenant defines maps to exactly one state, and it is the state Flow reasons about: the workspace filters, the wording the portal shows the customer and the turnaround figures all follow the state rather than the name the status was given.\nAWAITING_PRODUCT: the service order is registered, but the device has not arrived yet.\nNOT_STARTED: the device is here and the work is queued.\nIN_TRANSIT: the service order is in a box on its way between locations.\nIN_PROGRESS: the work is being done.\nWAITING_CUSTOMER: waiting for an answer from the customer — never for the device, which is what AWAITING_PRODUCT is for.\nWAITING_SUPPLIER: waiting for parts or for an answer from a supplier.\nREADY_FOR_PICKUP: the outcome is ready to be handed over.\nCOMPLETE: the customer has the device back. This is the handover, never the payment.\nCANCELLED: the service order ended without a completed repair.\nCOMPLETE and CANCELLED are the terminal states.\nThe states are owned by the platform: tenants never define or rename them, and only decide which state each of their own statuses maps to. A status that exists today keeps the state it has until the tenant remaps it — nothing is remapped for them.\nExtensible — new states may be added, so clients must not fail on a value they do not know.","type":"string","enum":["AWAITING_PRODUCT","NOT_STARTED","IN_TRANSIT","IN_PROGRESS","WAITING_CUSTOMER","WAITING_SUPPLIER","READY_FOR_PICKUP","COMPLETE","CANCELLED"]},"PriorityENUM":{"title":"PriorityENUM","description":"HIGHEST, HIGH and LOW rank the work; NONE is no priority, which a service order carries when none was given.","type":"string","enum":["HIGHEST","HIGH","LOW","NONE"]},"ServiceOrderTotalsSchema":{"title":"ServiceOrderTotalsSchema","description":"What the case comes to, split by whether the line was taken from tracked stock. Every figure is derived from the service order items on the case; there is no separate work registration behind any of them. All figures are in minor units of the tenant's currency: 150 is 1,50.","type":"object","required":["goodsTotal","grandTotal","otherTotal"],"properties":{"goodsTotal":{"type":"integer","description":"The lines whose product is of type STOCK — tracked in stock, what was taken from a warehouse for the work."},"grandTotal":{"type":"integer","description":"goodsTotal and otherTotal added together — what the case comes to."},"otherTotal":{"type":"integer","description":"Everything else billed on the case: labour, and any goods the tenant does not track in stock. The split is drawn on whether the product is tracked in stock, never on whether a person would call the line work — a SIM card the tenant does not keep stock of is counted here and not in goodsTotal, and an hour of labour is counted here because the product it is billed on is not stocked either. Neither figure is a statement about parts against labour, and nothing on a service order item records which of the two a line is."},"partsTotal":{"type":"integer","deprecated":true,"description":"Deprecated. Superseded by goodsTotal, and served as goodsTotal's value until removal. The meaning has changed with the name: it previously covered every line on the case, including the lines now counted in otherTotal. It will be removed after the announced deprecation window; see the deprecation log for the date."},"workTotal":{"type":"integer","deprecated":true,"description":"Deprecated. Superseded by otherTotal, and served as otherTotal's value until removal. The meaning has changed with the name: it was previously computed from the hours registered against the case, and hours registration is retired — work is an ordinary service order item like any other line. It will be removed after the announced deprecation window; see the deprecation log for the date."}}},"ServiceOrderItemResponseSchema":{"title":"ServiceOrderItemResponseSchema","type":"object","required":["serviceOrderItemUid","serviceOrderUid","warehouseUid","warehouseName","tenantUserUid","productUid","salesOrderUid","quantity","name","sku","coverImage","priceOverride","price","costPrice","originalPrice","discountReasonCode","discountReference","totalPrice","totalCostPrice","serialNumber","removedSerialNumber","note","orderItemReferenceValue","tenantUserDisplayName","createdAt","createdBy","lastModifiedAt","lastModifiedBy"],"properties":{"serviceOrderItemUid":{"$ref":"#/components/schemas/UID"},"serviceOrderUid":{"$ref":"#/components/schemas/UID"},"warehouseUid":{"type":"string","pattern":"^[A-Za-z0-9_-]*$","description":"The warehouse the line is booked against, fixed when the line was added. On a stock-tracked line it is where the units were taken out of, as the server resolved it — what the request named, else the store's serviceDefaultWarehouseUid service setting, else the store's own defaultWarehouseUid when the store owns that warehouse. A line for a product that is not stock-tracked carries one only when the request named it, and no stock moved. Empty when the line was added without one and needed none, or when the warehouse it carried has since been deleted. It cannot be changed afterwards, so it is read-only wherever an existing line is shown."},"warehouseName":{"type":"string","description":"The name of the warehouse the line is booked against, as it stands now — served beside warehouseUid so a line can be labelled without a second call, the way the sales order line carries it. Empty exactly when warehouseUid is empty. A store reading a case it does not own sees lines booked against the owning store's warehouses, which its own warehouse lists do not name, so the name travels with the line rather than being resolved by the reader."},"tenantUserUid":{"$ref":"#/components/schemas/OptionalUID"},"productUid":{"$ref":"#/components/schemas/UID"},"salesOrderUid":{"$ref":"#/components/schemas/OptionalUID"},"quantity":{"type":"integer","description":"In hundredths (100 = 1 unit)."},"name":{"type":"string"},"sku":{"type":"string"},"coverImage":{"description":"The cover image of the line's product as it stands now, carried on the line so a part can be drawn without a second read of the product. All three of its fields are empty strings when the product has no image: the object is always present and is never null, which is how the sales order line carries it too. It follows the product, so changing the product's cover image changes what every line for that product serves.","allOf":[{"$ref":"#/components/schemas/CoverImageSchema"}]},"priceOverride":{"type":"boolean","description":"Whether the line's product allows its price to be set to anything other than the one the catalogue resolves. It is read off the product as it stands now rather than recorded on the line, so it changes when the product does. It is the same flag the product carries, and on a service line it fixes the price outright rather than asking for the change to be recorded as a discount: a service line has no discount amount of its own, a reduction on a case being price below originalPrice, so there is nothing for it to be recorded as. False therefore fixes the line's price, and what a write then accepts is stated on price — the price the server resolves on the create, and on the update the price the line already carries, or its originalPrice where that is not 0. A client shows no price control on such a line rather than offering one the server will refuse. Where the case is billed, a reduction is given on the aggregate sales order line the part is summed into, the part being no line of its own there."},"price":{"type":"integer","description":"What one unit of the line is charged at now. It is originalPrice until somebody changes it, so the two together are the discount or the mark-up the line carries."},"costPrice":{"type":"integer"},"originalPrice":{"type":"integer","description":"What the line was priced at when it was added: the figure the create was sent, or the one the server resolved for it where the create sent none. It is written once, at the create, and no later change touches it — changing price moves price alone — so the two fields together say whether a line has been discounted or marked up since it was added, and by how much. A line that carries 0 here while it charges something holds no opening price at all, and the update never restores a price from that 0."},"discountReasonCode":{"type":"string","description":"The tenant's discount reason for this line's price, as its code — one of the tenant's reasons of type DISCOUNT, as the reasons list returns them for reasonType DISCOUNT. Empty when none was given. It is an annotation and never a calculation: what the line is charged at is price alone, and nothing is derived from the reason."},"discountReference":{"type":"string","description":"What the discount reason points at — the authorization, the agreement, the case it was granted under. Empty when none was given, and empty whenever discountReasonCode is empty. A line whose reason carries requiresReference always has one, that reason being refused on the line without it, so this is never empty while such a reason stands."},"totalPrice":{"type":"integer","description":"The price times the quantity divided by 100, the quantity being in hundredths."},"totalCostPrice":{"type":"integer"},"serialNumber":{"type":"string","description":"The serial number of the part that went INTO the device — the new part this line is for. An empty string when the part carries no serial number."},"removedSerialNumber":{"type":"string","description":"The serial number of the part that was taken OUT of the device and replaced by this one. An empty string when nothing was removed, or when the removed part carried no serial number.\nThe two serials are the two ends of the same swap: serialNumber is what went in, removedSerialNumber is what came out. Recording both is what lets the part that left the device be followed afterwards — back to the supplier on a claim, or found again if it turns up."},"note":{"type":"string"},"orderItemReferenceValue":{"type":"string","description":"The reference recorded against the line to satisfy what its product asks for — the phone number, the e-mail address, the agreement or the authorization the part is supplied under. An empty string when nothing was recorded, which is every line for a product that asks for nothing and every line added before a reference was demanded of it. Whether a product asks for a reference, and what shape the answer must take, is read off that product's own requirements.orderItemReference and never off the line."},"tenantUserDisplayName":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"lastModifiedAt":{"type":"string","format":"date-time"},"lastModifiedBy":{"type":"string"}}},"CoverImageSchema":{"title":"CoverImageSchema","type":"object","properties":{"productMediaUid":{"$ref":"#/components/schemas/OptionalUID"},"mainUrl":{"type":"string"},"thumbnailUrl":{"type":"string"}},"required":["productMediaUid","mainUrl","thumbnailUrl"]},"ServiceOrderAssetResponse":{"title":"ServiceOrderAssetResponse","type":"object","required":["assetUid","name","description","isPublic","customerVisible","publicUrl","mimeType","size","createdAt","createdBy","tenantUserUid","tenantUserDisplayName"],"properties":{"assetUid":{"$ref":"#/components/schemas/UID"},"name":{"type":"string"},"description":{"type":"string"},"isPublic":{"type":"boolean","description":"Whether the file is served from the CDN without a signature. For private assets use the download endpoint to get signed access. This is about how the file is fetched, not about who is meant to see it — customerVisible is what decides that."},"customerVisible":{"type":"boolean","description":"Whether the file is shown to the customer: it is listed among the documents on their service portal page. False keeps it inside the workshop — the photo of the damage taken for the technician, the supplier's note.\nIt is independent of isPublic, which only says whether the file is fetched from the CDN without a signature. A file can be CDN-public and still internal, and a customer-visible file reaches the customer through its own document link on the portal page. A file fetched from the CDN without a signature (isPublic) or kept at an address of its own, a file outside the SERVICE category and a file attached to more than one service order are never listed on the portal, whatever this flag says."},"publicUrl":{"type":"string"},"mimeType":{"type":"string"},"size":{"type":"string","description":"The size of the file in bytes, written as a decimal string — the same shape the asset endpoints serve it in."},"createdAt":{"type":"string","format":"date-time","description":"When the file record was created. It does not move when the file's contents are uploaded afterwards, nor when the file is attached to a service order later."},"createdBy":{"type":"string","description":"The caller the file record was created under, as it was recorded at the time: the name that user carries in this tenant, which on a shared screen is the person whose PIN unlocked it. A file created under an integration token carries that account's own name."},"tenantUserUid":{"type":"string","pattern":"^[A-Za-z0-9-_]*","description":"The person the file was added by: the caller, when the caller is a person — on a shared screen, the person whose PIN unlocked it. Empty when no person was resolved — an upload under an integration token, a file outside the SERVICE category, a file that carries no person. It is a person, never an integration account; createdBy is the caller's name as recorded at the time, a separate fact."},"tenantUserDisplayName":{"type":"string","description":"The display name of that person, read from the user as it stands today, the way a timeline entry names its author. Empty when tenantUserUid is empty."}}},"ServiceOrderFormValueResponseSchema":{"title":"ServiceOrderFormValueResponseSchema","description":"A value captured for one field on the service order's intake form, with the field's render information snapshotted at capture time so the value can be shown without looking the field up. Editing the field library afterwards does not change what is shown here.\nA capture for a MULTI_SELECT field is returned in values with value an empty string; every other field type is returned in value with values empty.\nA SECRET field never appears here: what was captured for one is a service order secret, listed under the service order's secrets and read through the secrets reveal endpoint.\nA sensitive value that was removed when the service order reached a terminal state still appears, with redacted true and an empty value: the answer's label stays, so the record still says which question was asked, and only what was answered is gone.","type":"object","required":["serviceFormFieldUid","fieldKey","label","fieldType","systemKind","sensitive","value","values","redacted"],"properties":{"serviceFormFieldUid":{"$ref":"#/components/schemas/UID"},"fieldKey":{"type":"string","description":"The stable slug of the field. It never changes."},"label":{"type":"string","description":"The label of the field as it was when the value was captured — for a platform field, in the language the capturing user read it in."},"fieldType":{"$ref":"#/components/schemas/ServiceFormFieldTypeENUM"},"systemKind":{"nullable":true,"description":"The well-known Flow concept this field represents. A system kind marks a platform field; null marks one of the tenant's own custom fields.","allOf":[{"$ref":"#/components/schemas/ServiceFormFieldSystemKindENUM"}]},"sensitive":{"type":"boolean","description":"Whether the value is sensitive. Sensitive values are left out of search results, webhooks and exports, and are removed when the service order reaches a terminal state — after which the entry stays, with redacted true."},"value":{"type":"string","description":"The captured value as text. Always an empty string for a capture on a MULTI_SELECT field, whose chosen options are in values. An empty string when redacted is true: the value has been removed and is not recoverable."},"values":{"type":"array","description":"The chosen options as text, for a capture on a MULTI_SELECT field. Empty for every other field type, and empty when redacted is true: what was chosen has been removed and is not recoverable.","items":{"type":"string"}},"redacted":{"type":"boolean","description":"True when a sensitive value was removed because the service order reached a terminal state. The answer's label remains, so the record still says which question was asked, and only what was answered is gone. False on every entry that still holds its value. The entry stays even when the placement it answers is later hidden by a condition — the record that the question was answered is kept — and, when the entry is marked sensitive, it is served on the single service order only, never in search results."}}},"ServiceFormFieldTypeENUM":{"title":"ServiceFormFieldTypeENUM","description":"The data type of a service order form field. It decides how the field is rendered at intake and how the captured value is validated.\nTEXT: single-line text. TEXTAREA: multi-line text. NUMBER: a number. BOOLEAN: true or false. DATE: an ISO 8601 date. SELECT: one of the values listed in the field's selectOptions. MULTI_SELECT: several of the values listed in the field's selectOptions. SECRET: a short sensitive text such as a device passcode.\nSECRET always implies sensitive, and what is captured for a SECRET field is never kept as a form value: it is stored as a service order secret, which holds the value encrypted, is listed by its label alone under the service order's secrets, and can only be read through the secrets reveal endpoint. Secrets are deleted when the service order reaches a terminal state.\nWhat is captured for a MULTI_SELECT field is a list rather than a single answer, and is sent and returned in the form value's values rather than in its value.\nThe field type is immutable after the field has been created — create a new field instead of changing it.","enum":["TEXT","TEXTAREA","NUMBER","BOOLEAN","DATE","SELECT","MULTI_SELECT","SECRET"],"type":"string"},"ServiceFormFieldSystemKindENUM":{"title":"ServiceFormFieldSystemKindENUM","description":"Marks a service order form field as a platform field: a well-known Flow concept, so clients and integrations can recognise the field without matching on its label.\nSERIAL_NUMBER: the serial number of the device. IMEI: the IMEI of a mobile device. PASSCODE: the passcode or unlock code of the device — it is of the field type SECRET and is therefore always sensitive. ACCESSORIES: the accessories handed in with the device, written as free text. LOCATION_LABEL: where the device is physically stored while it is in the workshop. ISSUE_REPRODUCED: whether the reported issue could be reproduced. INTERNAL_NOTE: a note that is never shown to the customer. ESTIMATED_PRICE: what the work is estimated to cost. MAX_PRICE: the ceiling the customer allows the work to be done up to without being asked again. PRODUCT: the product the unit in for service is. PRIORITY: how urgent the work is, one of the four values of PriorityENUM.\nA platform field is already in every tenant's field library, ready to be used. It belongs to the platform: the platform labels it — the label comes back localized to the reader's language, the way the platform localizes everywhere else — and the tenant can neither create, relabel nor archive one, nor change its validation or its sensitivity. Its whole definition — type, sensitivity and what it is checked against — is fixed by the platform and does not change, so a condition, a filter or an integration built against a platform field keeps working. No platform field carries selectOptions: what a tenant would put in such a list is the tenant's own, so a platform field is free text, a number, a boolean, a secret or, for PRIORITY, one of the four PriorityENUM values its kind names, and selectOptions is always empty on one. In every other respect a platform field is an ordinary field: it stands in the field list with a uid of its own, and it is placed on a form exactly like any other field, except that PRIORITY is never marked required.\nESTIMATED_PRICE and MAX_PRICE are backed fields: what is captured for them is written to the service order's own estimatedPrice and maxPrice rather than into the form value store, because that is where billing and the quote rules compute on it. The form still decides everything about the capture — whether the field appears at all, where in the intake it sits, and whether it must be filled in — and the value simply lands where it is used. Both are of field type NUMBER, and neither is ever sensitive.\nPRODUCT is backed in the same way: what is captured for it is written to the service order's own productUid rather than into the form value store, because that is what the case is identified by. It is of field type TEXT and is never sensitive. A PRODUCT capture is the uid of one of the tenant's products, and a uid naming none is refused with a 404, the same answer the service order's own productUid gives — the two are one road, so they refuse alike. A client renders it by its system kind — a product search — rather than as a text box.\nPRIORITY is backed in the same way: what is captured for it is written to the service order's own priority rather than into the form value store, because that is the priority the case carries wherever it is shown and filtered. It is of field type TEXT and is never sensitive. A PRIORITY capture is one of the names of PriorityENUM — HIGHEST, HIGH, LOW or NONE — and any other value is refused with a 400; clearing it stores NONE. A client renders it by its system kind — a picker of the four values — rather than as a text box, and its selectOptions stays empty. It is never a required field: the service order's priority always holds a value, so required would mean nothing, and a form placement that marks it required is refused with a 400. As with every backed field, a PRIORITY capture wins over priority sent in the same request, and a PRIORITY placement the write hides — hidden on the create, or closed by an update's own answers — clears it to NONE in the same write.\nA field without a system kind is a field the tenant created itself, and everything about it is the tenant's to set. The schema is nullable: null is the value systemKind carries on such a custom field.\nExtensible — new system kinds may be added, so clients must not fail on a value they do not know. A kind is seeded into every tenant's field library, so a client that lists the library meets every kind the platform has, including ones added after it was built.","enum":["SERIAL_NUMBER","IMEI","PASSCODE","ACCESSORIES","LOCATION_LABEL","ISSUE_REPRODUCED","INTERNAL_NOTE","ESTIMATED_PRICE","MAX_PRICE","PRODUCT","PRIORITY",null],"nullable":true,"type":"string"},"ServiceOrderSecretResponseSchema":{"title":"ServiceOrderSecretResponseSchema","description":"An encrypted secret kept on a service order, such as a device passcode. A service order can hold any number of secrets.\nThe value is never returned here: a secret is listed by its label alone and can only be read through the secrets reveal endpoint, which writes a SECRET_VIEWED event on the service order.\nA secret is never edited. It is created, revealed and deleted, and nothing else — a value that has to change is a new secret and the old one deleted, which is why the record carries when it was created and by whom and nothing about a later change.\nSecrets are never included in search results, webhooks or exports. A secret can be deleted on its own as soon as it is no longer needed, and every secret still on the service order is deleted in the same transaction as the service order reaching a terminal state. Reopening the service order afterwards does not bring them back.","type":"object","required":["secretUid","serviceOrderUid","label","serviceFormFieldUid","createdAt","createdBy"],"properties":{"secretUid":{"$ref":"#/components/schemas/UID"},"serviceOrderUid":{"$ref":"#/components/schemas/UID"},"label":{"type":"string","description":"What the secret is, so it can be shown in a list without revealing the value. Snapshotted when the secret is created — editing the label of the form field afterwards does not change it, and a platform field's label snapshots in the language the capturing user read it in."},"serviceFormFieldUid":{"description":"The SECRET field on the intake form this secret was captured for. An empty string on a secret that was added on its own, without a form field.","type":"string","pattern":"^[A-Za-z0-9-_]*"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"}}},"ServiceJobTypeRefSchema":{"title":"ServiceJobTypeRefSchema","description":"A job type on a service order, with its name embedded so the service order can be shown without looking the job type up. Read the checklist and the suggested products through the job types endpoint.","type":"object","required":["serviceJobTypeUid","name"],"properties":{"serviceJobTypeUid":{"$ref":"#/components/schemas/UID"},"name":{"type":"string","description":"The name of the job type as it reads now. Renaming the job type changes what is returned here."}}},"ServiceOrderChecklistAnswerSchema":{"title":"ServiceOrderChecklistAnswerSchema","description":"One answer on a job type's checklist on a service order: the item, yes or no, who gave the answer and when. It is internal staff data and never reaches a customer.\nAttribution is double, as on a timeline entry. tenantUserUid is the person who gave the answer: the caller, which on a shared screen is the person whose PIN unlocked it; it is empty for an answer given under an integration token, which is no person, and empty when that person has since been deleted. tenantUserDisplayName is that person's name as it read when the answer was given: it stays when the person is deleted, and is empty under an integration token. screenAccountTenantUserUid is the screen account the screen was signed in with, set exactly as on a timeline entry: under an unlock token it is read from the token; on a personal login, or under any other access token, it is the person themselves; it is empty under an integration token, and empty when the screen account has since been deleted.","type":"object","required":["serviceJobTypeUid","item","checked","tenantUserUid","tenantUserDisplayName","screenAccountTenantUserUid","answeredAt"],"properties":{"serviceJobTypeUid":{"$ref":"#/components/schemas/UID"},"item":{"type":"string","description":"The checklist item the answer is for, as it reads on the job type."},"checked":{"type":"boolean","description":"The answer: true is yes, false is no."},"tenantUserUid":{"$ref":"#/components/schemas/OptionalUID"},"tenantUserDisplayName":{"type":"string"},"screenAccountTenantUserUid":{"$ref":"#/components/schemas/OptionalUID"},"answeredAt":{"type":"string","format":"date-time","description":"When the answer was given. Sending the same answer again does not move it; a different answer does."}}},"ServiceOrderQuoteStateENUM":{"title":"ServiceOrderQuoteStateENUM","description":"Where the latest quote on a service order stands.\nNONE: nothing has been offered. SENT: an offer is outstanding, waiting for the customer's answer. APPROVED: the customer accepted the latest offer. DECLINED: the customer turned it down.\nIt is maintained solely by the quote endpoints: sending a quote sets SENT, recording a decision sets the answer that was given, and a new quote puts it back to SENT and asks again.","type":"string","enum":["NONE","SENT","APPROVED","DECLINED"]},"ServiceOrderCoverageENUM":{"title":"ServiceOrderCoverageENUM","description":"Who pays for the work on a service order, and under what right. Every service order carries a coverage: it is chosen at intake, and it can be changed while the case runs — a diagnosis may reclassify it, as when water damage voids a warranty and the case becomes payable.\nPAYABLE: the customer pays. CONSUMER_CLAIM: a claim under statutory consumer rights — the seller's cost. WARRANTY_CLAIM: a claim under a warranty — the seller's or the manufacturer's cost. INSURANCE_CLAIM: a claim to an insurer — the service order names the insurer and may carry the insurer's claim number and the deductible, recorded for information.\nPricing, quoting and invoicing work the same way whatever the coverage is: the sale is billed to the service order's customer on every coverage (see post-serviceorders-orders). A covered repair can still carry billable work.\nExtensible — new coverages may be added, so clients must not fail on a value they do not know.","type":"string","enum":["PAYABLE","CONSUMER_CLAIM","WARRANTY_CLAIM","INSURANCE_CLAIM"]},"ServiceEventResponseSchema":{"title":"ServiceEventResponseSchema","description":"An entry in the service order timeline. Every event has an eventType saying what it records; events created before event types existed are returned as STATUS_CHANGED.\nserviceStatusUid is set on the events that carry a status — the first entry the create writes, and every later STATUS_CHANGED and REOPENED. On every other event it is an empty string.\nAttribution is always double. tenantUserUid is the person who did the work: the caller, which on a shared screen is the person whose PIN unlocked it. It is empty on an entry no person wrote — one written under an integration token, which is no person, one the customer made in the service portal, and the creation entry of a service order whose create recorded no one — and empty when that person has since been deleted. screenAccountTenantUserUid is the screen account: the login the screen was signed in with when the work was done, which on the web app is the identity-provider login the browser holds. Under an unlock token it is read from the token; on a personal login, or under any other access token, it is the person themselves; it is empty wherever tenantUserUid is empty for want of a person, and empty when the screen account has since been deleted. Both are set when the entry is written and never edited. createdBy is not a third identity: it is the caller's display name as text, the audit column every record carries.","type":"object","required":["serviceEventUid","serviceOrderUid","serviceStatusUid","tenantUserUid","tenantUserDisplayName","screenAccountTenantUserUid","note","visibility","createdAt","createdBy","lastModifiedAt","lastModifiedBy","edited","notifications","eventType","checklistResults","quoteAmount","quoteDecisionVia","serviceJobTypeUid","movedFromStoreUid","movedToStoreUid"],"properties":{"serviceEventUid":{"$ref":"#/components/schemas/UID"},"serviceOrderUid":{"$ref":"#/components/schemas/UID"},"serviceStatusUid":{"description":"The status this event put the service order on: the status it started on, on the first entry the create writes, and the status it was moved to, on every later STATUS_CHANGED and REOPENED; an empty string on every other event.","type":"string","pattern":"^[A-Za-z0-9-_]*"},"tenantUserUid":{"$ref":"#/components/schemas/OptionalUID"},"tenantUserDisplayName":{"type":"string"},"screenAccountTenantUserUid":{"$ref":"#/components/schemas/OptionalUID"},"note":{"type":"string"},"visibility":{"$ref":"#/components/schemas/VisibilityENUM"},"createdAt":{"type":"string","format":"date-time"},"createdBy":{"type":"string"},"lastModifiedAt":{"type":"string","format":"date-time"},"lastModifiedBy":{"type":"string"},"edited":{"type":"boolean","description":"Whether the event has been changed since it was written — the edited marker a chat message carries, and read the same way: the entry still stands, but it does not read as it first did. Who changed it and when are lastModifiedBy and lastModifiedAt; what it said before is not kept, so the marker is the whole of the disclosure.\nIt is set only by the event update endpoint. It is never derived from comparing timestamps, so an event that Flow touched for its own reasons — a backfill, a migration, a reindex — is never marked as edited by a person.\nBecause only the note and the visibility of an editable event can be changed at all, the marker only ever appears on entries a person wrote: STATUS_CHANGED, REOPENED, NOTE, DIAGNOSIS, RESOLUTION and STORE_MESSAGE. Events Flow writes itself are immutable and always return false."},"notifications":{"type":"array","items":{"$ref":"#/components/schemas/ServiceEventNotificationResponseSchema"}},"eventType":{"description":"What this timeline event records. Events created before event types existed are returned as STATUS_CHANGED.","allOf":[{"$ref":"#/components/schemas/ServiceEventTypeENUM"}]},"checklistResults":{"type":"array","description":"The job type checklist that was run, as recorded at the submit: one entry per item on the job type's checklist, each the result the submit carried or, where it carried none, the answer the item held on the service order. The entries are snapshotted so that editing the job type afterwards does not change what the event records.\nOnly a CHECKLIST_COMPLETED event carries results. It is an empty array on every other event, a CHECKLIST_SKIPPED event included — a skip records that the checklist was not run, by whom and when, and has nothing to report per item.","items":{"$ref":"#/components/schemas/ServiceEventChecklistResultSchema"}},"quoteAmount":{"type":"integer","nullable":true,"description":"The amount the quote stood at when this event was written, in the same unit as estimatedPrice and maxPrice on the service order. It is snapshotted at that moment, so a quote that is replaced afterwards never rewrites what an earlier event records — the timeline answers what was offered and what was answered, each at its own time.\nOnly QUOTE_SENT, QUOTE_APPROVED and QUOTE_DECLINED events carry an amount. It is null on every other event."},"quoteDecisionVia":{"type":"string","description":"Where the customer's answer to the quote was given, so the record says how it reached the store.\nPORTAL: the customer answered with the service portal's buttons. IN_PERSON: the customer answered over the counter. PHONE: the customer answered on the phone. WRITTEN: the customer answered in writing outside the portal's buttons — in a portal message, an SMS reply or an e-mail.\nIt reports all four origins, which is more than staff are ever asked to send. PORTAL is written by the portal itself; IN_PERSON, PHONE and WRITTEN are the three a person records, and they are the values of ServiceQuoteDecisionViaENUM.\nOnly QUOTE_APPROVED and QUOTE_DECLINED events carry it. It is an empty string on every other event, QUOTE_SENT included — sending a quote is not a decision.","enum":["","PORTAL","IN_PERSON","PHONE","WRITTEN"]},"serviceJobTypeUid":{"description":"The job type whose checklist this event records. Set on CHECKLIST_COMPLETED and CHECKLIST_SKIPPED events, which is how the outstanding checklists on a service order are worked out: the job types it carries, minus the ones a CHECKLIST_ event already names.\nAn empty string on every other event.","type":"string","pattern":"^[A-Za-z0-9-_]*"},"movedFromStoreUid":{"description":"The store the service order left, on a MOVED event. An empty string on every other event.","type":"string","pattern":"^[A-Za-z0-9-_]*"},"movedToStoreUid":{"description":"The store the service order went to, on a MOVED event. An empty string on every other event.","type":"string","pattern":"^[A-Za-z0-9-_]*"}}},"VisibilityENUM":{"title":"VisibilityENUM","description":"Who may read a timeline entry.\nINTERNAL: staff only — never in the service portal and never on the printed service papers. PUBLIC: shown to the customer, in the portal and on the printed papers alike. REPORT: printed on the service papers only — the service summary and the damage report, since the service confirmation carries no written entries — and never shown in the service portal.\nOn a NOTE, a DIAGNOSIS or a RESOLUTION the visibility says only where the entry is shown and never sends the customer anything. The papers carry written entries only — a NOTE, a DIAGNOSIS or a RESOLUTION — so what a visibility says about paper applies to those three. On every other event type the visibility governs the service portal alone: a status change carrying REPORT is accepted and simply does not print. A STORE_MESSAGE is not a written entry: it is always PUBLIC, shown in the portal and never printed on any paper.","type":"string","enum":["INTERNAL","PUBLIC","REPORT"]},"ServiceEventNotificationResponseSchema":{"title":"ServiceEventNotificationResponseSchema","type":"object","required":["notificationUid","type","state","recipient"],"properties":{"notificationUid":{"$ref":"#/components/schemas/UID"},"type":{"$ref":"#/components/schemas/MessageTypeENUM"},"state":{"$ref":"#/components/schemas/NotificationStateENUM"},"recipient":{"type":"string"}}},"MessageTypeENUM":{"title":"MessageTypeENUM","type":"string","enum":["EMAIL","SMS"]},"NotificationStateENUM":{"title":"NotificationStateENUM","type":"string","enum":["NOT_STARTED","IN_PROGRESS","COMPLETE","FAILED"]},"ServiceEventTypeENUM":{"title":"ServiceEventTypeENUM","description":"What a service order timeline event records. Every event has exactly one event type.\nSTATUS_CHANGED: the event put the service order on the status it carries. The first entry on every timeline is a STATUS_CHANGED written by the create, carrying the status the service order started on, with the visibility INTERNAL; it is edited like any other status change. NOTE: a note written by staff, carrying no status change. ASSIGNED: the service order was handed to another user, or unassigned, through the assign endpoint. It is written with the visibility INTERNAL and carries no status, and its note names by display name the user it went to, or records that it went back to the store on an unassign, in the tenant's own language. REOPENED: a status change that moves the service order out of a terminal state, COMPLETE or CANCELLED, is recorded and returned as REOPENED rather than as STATUS_CHANGED. It carries the status it moved to exactly as any other status change does, and it is the one Flow-typed event a person authors: reopening is the status change, there is no separate reopen action, and a service order is never reopened by anything other than someone moving it. DIAGNOSIS: what the technician found to be wrong. RESOLUTION: what was done to resolve the issue. FIELD_CHANGE: one of the tracked fields on the service order was changed. The tracked fields are claimNumber, coverage, customerUid, deductible, externalSalesOrderNumber, priority, readyAtEstimate and title; changing any other field leaves no event. One event is written per changed field, so an update that changes three of them leaves three events. Each is written with the visibility INTERNAL and carries no status, and its note says which field changed and from what to what, in the tenant's own language. A change of customer names the customer before and the customer after. A change of the deductible names the amount before and after in the tenant's currency, as an amount, never in minor units. CUSTOMER_MESSAGE: a free-text message the customer wrote in the service portal. STORE_MESSAGE: a message the store wrote to the customer — the store's half of the dialogue in the service portal, beside CUSTOMER_MESSAGE. It is always PUBLIC and carries no status; it carries its author on the staff timeline like any staff entry, while the portal never shows who wrote it, and it is never printed on any paper. TERMS_ACCEPTED: the customer accepted the terms and conditions on the service order — in the service portal, on a counter device or in the store. TERMS_VOIDED: the acceptance recorded on the service order was voided through the void endpoint, or by a change of the service order's customer — the note carries the reason given, while who voided it and when are the event's own attribution and timestamp. QUOTE_SENT: a price was offered to the customer, whether or not a message went out with it. QUOTE_APPROVED: the customer approved the quote. QUOTE_DECLINED: the customer declined the quote. All three are written with the visibility PUBLIC and carry no status: the customer sees the offer and their own answer in the portal history, each entry carrying the amount it stood at, and each with an empty note of its own — the portal words the entry from its type. CHECKLIST_COMPLETED: a job type checklist was completed through the service order's checklist endpoint, with the result of every item snapshotted on the event. CHECKLIST_SKIPPED: a job type checklist was skipped through the service order's checklist endpoint, after the user confirmed the skip. SECRET_VIEWED: a secret on the service order was revealed through the secrets reveal endpoint. SECRET_DELETED: a secret on the service order was deleted through the secrets delete endpoint before the service order finished; the note carries the label of the secret that was removed, so the timeline says what was taken off the case and by whom. MOVED: the service order was moved to another store through the move mechanism, whether by the move endpoint or by the receipt of a shipment item at another store. It is written with the visibility INTERNAL and carries no status; movedFromStoreUid and movedToStoreUid record the store it left and the store it went to, and its note records the assignment the move ended, in the tenant's own language.\nOnly STATUS_CHANGED, NOTE, DIAGNOSIS, RESOLUTION and STORE_MESSAGE can be sent when an event is created — every other type is written by Flow when the corresponding action happens, REOPENED included, which is what a STATUS_CHANGED out of a terminal state is recorded as. Editing reaches those five and REOPENED with them; every other type is immutable.","enum":["STATUS_CHANGED","NOTE","ASSIGNED","REOPENED","DIAGNOSIS","RESOLUTION","FIELD_CHANGE","CUSTOMER_MESSAGE","STORE_MESSAGE","TERMS_ACCEPTED","TERMS_VOIDED","QUOTE_SENT","QUOTE_APPROVED","QUOTE_DECLINED","CHECKLIST_COMPLETED","CHECKLIST_SKIPPED","SECRET_VIEWED","SECRET_DELETED","MOVED"],"type":"string"},"ServiceEventChecklistResultSchema":{"title":"ServiceEventChecklistResultSchema","description":"The result of one checklist item, snapshotted on the event when the checklist was submitted. Editing the job type's checklist afterwards does not change what was recorded.","type":"object","required":["item","checked"],"properties":{"item":{"type":"string","description":"The checklist item as it read on the job type when the checklist was submitted."},"checked":{"type":"boolean","description":"Whether the test passed. False records an item that was run and did not pass, which stays on the timeline together with the rest of the checklist."}}},"ServiceEventCreateRequestSchema":{"allOf":[{"$ref":"#/components/schemas/ServiceEventUpdateRequestSchema"},{"properties":{"serviceEventUid":{"description":"The uid the event is written under. Generate it before the first attempt and reuse the same value on every retry: a second request carrying a uid that is already on the timeline is a no-op answered with the service order unchanged, so a retry after a timeout can never write the same note or the same status change twice.","type":"string","pattern":"^[A-Za-z0-9-_]+"},"serviceStatusUid":{"description":"The status to move the service order to. Leave it out, or send an empty string, to write an event that carries no status change. A deprecated status is refused with a 400, unless it is the status the service order is already at, which does nothing.\nSending the status the service order is already at does nothing: no event is written, no status rule fires and no message goes out. A double tap on the same status, or a retry of a request that did land, changes nothing the second time. Deliberately sending the customer the same message again is the send endpoint's job, not this one's.","type":"string","pattern":"^[A-Za-z0-9-_]*"},"eventType":{"description":"What this timeline event records. When it is left out the event becomes STATUS_CHANGED if a serviceStatusUid is given, and NOTE if it is not.\nOnly STATUS_CHANGED, NOTE, DIAGNOSIS, RESOLUTION and STORE_MESSAGE are accepted here. Every other type is written by Flow when the corresponding action happens and is rejected on this endpoint.\nSTORE_MESSAGE is a message the store writes to the customer, and it is never what a left-out eventType becomes: a message to the customer names it. It is always PUBLIC and carries no status: one sent with any other visibility is refused with a 400 and writes nothing at all, and so is one whose note is empty or holds nothing but whitespace. It is never printed on any paper.\nA STATUS_CHANGED that moves the service order out of a terminal state is recorded and returned as REOPENED. The reopen is the status change, and there is nothing else to send for it.","allOf":[{"$ref":"#/components/schemas/ServiceEventAuthorableTypeENUM"}]},"notifications":{"type":"array","description":"The channels to send a STORE_MESSAGE to the customer on. Leave it out, or send an empty list, to write the entry without sending the customer anything.\nA channel can only be named on a STORE_MESSAGE. A channel named on any other type — a STATUS_CHANGED, a status change that is recorded as REOPENED included, a NOTE, a DIAGNOSIS or a RESOLUTION — is refused with a 400 and writes nothing at all, the entry it carried included, whatever its visibility. A status change messages the customer only through the status rule on the status it moves to, in the text the tenant wrote for that rule, and that message carries no note. A written entry is shown to the customer only where its visibility puts it.\nThe message is composed from the tenant's own text for SMS_SERVICE_ORDER_UPDATED on SMS and EMAIL_SERVICE_ORDER_UPDATED on e-mail, the STORE_MESSAGE's note filling .serviceNote. In the e-mail every line break of the note is a line break, as it is in the SMS; the note is escaped as every value written into an e-mail is. Those are service template types like any other: the tenant writes the text under those exact names, the platform catalog does not list them, and nothing stands behind them until the tenant writes it. A request naming a channel the tenant has written no text for is refused with a 400 and writes nothing at all, the entry it carried included, exactly as a request made while messages are paused is.\nA STORE_MESSAGE is always PUBLIC, so the message only ever quotes an entry the portal shows the customer. A STORE_MESSAGE that names no channel is shown in the portal and nothing is sent.\nWhile the tenant's service messages are paused — serviceMessagesPaused in the service settings — a request that names a channel is refused with a 400 and writes nothing at all: the STORE_MESSAGE it carried is not written either. Sent again without a channel, it is shown in the portal and nothing is sent. An event that names no channel is written as usual while messages are paused.","items":{"$ref":"#/components/schemas/MessageTypeENUM"}},"notifyAssignee":{"type":"boolean","description":"Set to true to tell the person on the case about this event. When the service order is assigned, that is the assignee: they get a notification in their Flow inbox, of type NOTE, which never leaves Flow, and an e-mail to their own address. When nobody is assigned, the service staff of the store that owns it get the inbox notification, and one e-mail goes to the store's own e-mail address as configured on the store; a store without an e-mail address gets the inbox notifications only. The e-mail is an ordinary e-mail send, recorded in the notification log alongside the messages that go to customers. This is how a colleague is pulled in on something written on a service order they are not watching.\nIt has nothing to do with notifications, which messages the CUSTOMER on SMS or e-mail. One reaches the person on the case, the other reaches the customer, and they are set independently."}}}],"type":"object","title":"ServiceEventCreateRequestSchema","description":"A new entry on the service order timeline: a status change, a note, a diagnosis, a resolution or a message from the store to the customer, together with what the customer and the person on the case are told about it.\nEverything that is decided once, at the moment the event happens, is set here and only here — the status it carries, the kind of event it is, and who is messaged about it. Afterwards only what the event says and who may read it can still be changed.\nSupply serviceEventUid to make the call idempotent."},"ServiceEventUpdateRequestSchema":{"title":"ServiceEventUpdateRequestSchema","description":"The editable parts of a timeline event: what it says, and who is allowed to read it.\nA PUT is accepted on the entries a person authored — STATUS_CHANGED, REOPENED, NOTE, DIAGNOSIS, RESOLUTION and STORE_MESSAGE — and reaches the same two fields on each of them, within the rule a STORE_MESSAGE carries. Every other event type is history Flow wrote when something actually happened, and a PUT on one is refused with a conflict: system-written history is immutable.\nOn a STORE_MESSAGE the note can be corrected — the portal shows the corrected text, and the message that already went out is neither sent again nor changed — but the visibility cannot: a STORE_MESSAGE is always PUBLIC, PUBLIC is accepted on it, and any other visibility on one is refused with a 400, as is a note that is empty or holds nothing but whitespace.\nWhat an event carries is fixed when it is written and is not editable either — the status it put the service order on, the kind of event it is, and the messages that went out with it. A service order that is on the wrong status is corrected by writing the next status change, never by rewriting the last one.","type":"object","required":["visibility"],"properties":{"note":{"type":"string","description":"What the event says. Editing it corrects the wording of a note that is already on the timeline. A customer who may read the event sees the corrected text the next time they open the portal — it never recalls a message that has already been sent."},"visibility":{"description":"Who may read the event. On every authored entry but a STORE_MESSAGE all three visibilities can be set, and in any direction: INTERNAL takes the entry off the service portal and off the printed service papers, PUBLIC puts it on both, and REPORT keeps it off the portal but on the papers — the service summary and the damage report, since the service confirmation carries no written entries. The papers carry written entries only — a NOTE, a DIAGNOSIS or a RESOLUTION — so on every other event type the visibility governs the portal alone, and a status change carrying REPORT is accepted and simply does not print. A STORE_MESSAGE is always PUBLIC, never printed, and any other visibility on one is refused with a 400. A note written as PUBLIC by mistake is made internal again by sending INTERNAL, and an internal note that turns out to belong in front of the customer is opened by sending PUBLIC. A change takes effect from that moment on; it does not unsend a message that has already gone out.\nThere is no default here: every request names a visibility, and a request that omits it is refused rather than defaulted. Whatever the visibility, what is written into the note prints as written, and nothing is stripped out of it.\nOn the event create, the visibility also decides whether a notifications channel may be named at all: a channel is accepted only on a STORE_MESSAGE, which is always PUBLIC, and a channel named on any other type is refused with a 400 that writes nothing. Changing the visibility here sends nothing and stops nothing: what went out with the entry was fixed when it was written.","allOf":[{"$ref":"#/components/schemas/VisibilityENUM"}]}}},"ServiceEventAuthorableTypeENUM":{"title":"ServiceEventAuthorableTypeENUM","description":"The event types a person can write on the service order timeline: STATUS_CHANGED, NOTE, DIAGNOSIS, RESOLUTION and STORE_MESSAGE, with the meanings ServiceEventTypeENUM gives them.\nThese five are the whole authorable surface. Every other type on ServiceEventTypeENUM is written by Flow when the corresponding action happens, REOPENED included — a STATUS_CHANGED that moves the service order out of a terminal state is recorded as REOPENED by the server, never sent as one.","enum":["STATUS_CHANGED","NOTE","DIAGNOSIS","RESOLUTION","STORE_MESSAGE"],"type":"string"}},"requestBodies":{"ServiceEventCreateRequest":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceEventCreateRequestSchema"}}}}}},"paths":{"/tenants/{tenantUid}/stores/{storeUid}/serviceorders/{serviceOrderUid}/events":{"post":{"summary":"Create new service-order event","operationId":"post-serviceorders-events","description":"Write a new entry on the service order timeline: a status change, a note, a diagnosis, a resolution or a message from the store to the customer. Only those five event types are accepted here — every other type is written by Flow when the corresponding action happens. A status change that moves the service order out of a terminal state is recorded and returned as REOPENED rather than as STATUS_CHANGED.\nA message to the customer is a STORE_MESSAGE: the store's half of the dialogue in the service portal, beside the customer's own CUSTOMER_MESSAGE. It is always PUBLIC — a STORE_MESSAGE with any other visibility is refused with a 400 and writes nothing at all, whether or not it names a channel. It carries no status, as no entry but a status change does. A STORE_MESSAGE whose note is empty or holds nothing but whitespace is refused with a 400 and writes nothing at all. It is never printed on any paper. Named with a channel it is sent to the customer as well as shown in the portal; named without one it is shown in the portal and nothing is sent.\nCustomer channels are accepted on a STORE_MESSAGE alone. A channel named on a STATUS_CHANGED, a status change that is recorded as REOPENED included, or on a NOTE, a DIAGNOSIS or a RESOLUTION, is refused with a 400 and writes nothing at all, whatever its visibility. A status change messages the customer only through the status rule on the status it moves to, in the text the tenant wrote for that rule and with no note, so one status change never sends the customer two messages. Those three are the written entries, and their visibility says only where they are shown: INTERNAL on the case alone, REPORT on the papers alone, PUBLIC in the portal and on the papers. The papers carry written entries only, so a STORE_MESSAGE is never on one.\nWhile the tenant setting termsAcceptanceRequired is on, every status change into the state IN_PROGRESS on a service order that has terms is refused until a standing acceptance exists on it — not only the first. A service order already in work is never un-started by a void, but once it leaves IN_PROGRESS it does not come back in until the terms are accepted afresh. However the acceptance is given — in the service portal, on a counter device or in the store — it has to stand before the work can start, and an acceptance that has been voided no longer satisfies anything. A service order without terms is never held, and while the setting is off nothing waits on the terms.\nCreating a STATUS_CHANGED event with the status the service order already has is a no-op. No event is written, no status rule fires and no message goes out, and the answer is 200 with the service order unchanged. Sending the customer the same message again on purpose is what the send endpoint is for.\nThe call is idempotent on serviceEventUid: generate the uid before the first attempt and reuse it on every retry. A retry of a request that did land falls into the same no-op — it is answered 200 with the service order unchanged, and nothing is written a second time. A double tap on the same status is answered the same way.\nAn event is immutable once written apart from its note and its visibility, which are edited through the update endpoint. Nothing else about it can be changed afterwards.\nAt the store the service order was taken in at, a NOTE is the one entry that may be added, and every other entry type, a STORE_MESSAGE included, is refused there with a 403; at a store that neither owns the service order nor took it in, every entry, a NOTE included, is refused with a 403. A STORE_MESSAGE is therefore written at the owning store alone. Editing or deleting an entry belongs to the owning store.\nThe request's own refusals come first, answered before the store is judged — among them an event type outside the five, an unknown visibility, a serviceStatusUid naming no status of the tenant and a malformed serviceEventUid. The store is judged next, and only then what the entry would change: at the store the service order was taken in at, an entry that is not a NOTE is refused with a 403, and at a store that neither owns the service order nor took it in every entry is, even when recording it there would have changed nothing. The messages switch is judged there too: while the tenant's service messages are paused, an entry naming a channel is refused even when the entry itself would have changed nothing. The entry-type and visibility rules are judged in the same place: a channel named on any type but a STORE_MESSAGE — a status change to the status the service order already has included — and a STORE_MESSAGE with any visibility but PUBLIC are refused even when the entry itself would have changed nothing. The text behind the channel is judged there too: a channel the tenant has written no text for is refused even when the entry itself would have changed nothing.\n\nRequired permissions: SERVICE_ORDER","responses":{"200":{"$ref":"#/components/responses/ServiceOrderResponse"},"201":{"$ref":"#/components/responses/ServiceEventResponse"},"409":{"description":"Conflict. The status change takes the service order into the state IN_PROGRESS while the tenant setting termsAcceptanceRequired is on, the service order has terms, and no acceptance stands on it: work cannot start before the customer has accepted the terms. Nothing is written."}},"requestBody":{"$ref":"#/components/requestBodies/ServiceEventCreateRequest"},"tags":["Service"]}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.flowretail.com/docs/developers/api-documentation/service/create-new-service-order-event.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
