API Documentation External

Dalinea API

v1.0.0 External

Unified API for the Dalinea platform. All endpoints require a valid JWT bearer token. Tenant isolation is enforced via the x-tenant-id header extracted from the token.

Getting Started

Welcome to the Dalinea API. This guide covers the basics of making requests and understanding responses.

Base URL

All API requests are made to:

https://api.kloopify.com

Request Format

The API accepts JSON request bodies and returns JSON responses. Set the following headers on every request:

Content-Type: application/json
Authorization: Bearer <your-token>

Response Format

Successful responses return a 2xx status code with a JSON body. Error responses include a message describing the issue:

{
  "statusCode": 400,
  "message": "Validation failed",
  "error": "Bad Request"
}

Pagination

List endpoints support pagination via offset and limit query parameters:

Parameter Default Description
offset 0 Number of records to skip
limit 100 Number of records to return (max 100)

Rate Limiting

API requests are rate-limited per account. If you exceed the limit, you'll receive a 429 Too Many Requests response. Back off and retry after the duration indicated in the Retry-After header.

Authentication

The Dalinea API uses JWT Bearer tokens for authentication. Every request must include a valid token in the Authorization header.

Obtaining a Token

Tokens are issued by our identity provider. Authenticate with your credentials to receive an access token:

POST https://auth.kloopify.com/oauth/token
Content-Type: application/json

{
  "client_id": "<your-client-id>",
  "client_secret": "<your-client-secret>",
  "audience": "https://api.kloopify.com",
  "grant_type": "client_credentials"
}

The response includes an access_token and its expiry:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 86400
}

Using the Token

Include the token in the Authorization header of every API request:

Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

Token Expiry

Tokens expire after the duration specified in expires_in (in seconds). When a token expires, the API returns 401 Unauthorized. Request a new token and retry.

Scopes

Access is controlled by scopes embedded in the token. If your token lacks the required scope for an endpoint, the API returns 403 Forbidden.

Running a Cost Analysis

This guide walks through submitting a cost analysis job, tracking its progress, and retrieving the results.

Overview

A cost analysis is an asynchronous operation. You submit a request describing a product and its manufacturer, the system processes it in the background, and you poll for the result. Once complete, the result contains IDs you can use to fetch the full cost analysis, bill of materials, product, and manufacturer records.

Step 1: Submit the Job

Create a cost analysis by posting to the pipeline endpoint:

POST /pipeline/cost-analysis
Content-Type: application/json
Authorization: Bearer <your-token>
{
  "productName": "EcoBottle 500ml",
  "manufacturerName": "GreenPack Industries",
  "countryOfOrigin": "DE",
  "productContext": "Reusable water bottle made from recycled stainless steel",
  "pricesPaid": [
    { "price": 1250, "currency": "USD", "date": "2025-01-15" }
  ]
}

The only required fields are productName and manufacturerName. Providing additional context improves the accuracy of the analysis:

Field Type Description
productName string Required. Name of the product
manufacturerName string Required. Name of the manufacturer
supplierNames string[] Supplier name(s)
productWebsite string URL for the product page
manufacturerWebsite string URL for the manufacturer
productContext string Free-text description of the product
manufacturerContext string Free-text description of the manufacturer
supplierContext string Free-text description of the supplier(s)
purchaserContext string Context about the purchaser
countryOfOrigin string ISO country code (defaults to US)
pricesPaid object[] Historical prices — each with price (number, required), currency (string, required), and date (string, optional)

The response is 201 Created with a job object:

{
  "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
  "type": "cost-analysis",
  "status": "pending",
  "step": null,
  "result": null,
  "started": null,
  "completed": null
}

Save the id — you'll use it to check on the job.

Step 2: Poll for Completion

Check the status of your job by its ID:

GET /pipeline/cost-analysis/{id}
Authorization: Bearer <your-token>

The status field progresses through these values:

Status Meaning
pending Job is queued
in_progress Job is actively processing
completed Job finished successfully
failed Job encountered an error

Poll every 5–10 seconds until status is completed or failed. The step field provides a human-readable description of what the system is currently working on.

When the job completes, the response includes a result object with IDs for all generated records:

{
  "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
  "type": "cost-analysis",
  "status": "completed",
  "step": "done",
  "result": {
    "costAnalysisId": "ca-uuid-here",
    "bomId": "bom-uuid-here",
    "productId": "product-uuid-here",
    "manufacturerId": "manufacturer-uuid-here"
  },
  "started": "2025-06-01T12:00:00.000Z",
  "completed": "2025-06-01T12:01:30.000Z"
}

If the job fails, errorMessage describes what went wrong.

Step 3: Retrieve the Results

Use the IDs from the result to fetch the generated records.

Cost Analysis

GET /cost-analysis/{costAnalysisId}
Authorization: Bearer <your-token>

Returns the cost analysis record including the shouldCostAmount and shouldCostStdErr (both in minor currency units, e.g. cents), the currency, and the analysisDate.

Bill of Materials

GET /bom/{bomId}
Authorization: Bearer <your-token>

Returns the bill of materials with a components list. Each component includes its name, type, cost, quantity, unitType, and countryOfOrigin.

To retrieve the full component details separately:

GET /bom/{bomId}/components
Authorization: Bearer <your-token>

Product and Manufacturer

The productId and manufacturerId can be used to retrieve the product and manufacturer (company) records:

GET /product/{productId}
GET /company/{manufacturerId}

Error Handling

  • 404 Not Found — The job or record ID does not exist.
  • 401 Unauthorized — Token is missing or expired. See the Authentication guide.
  • status: "failed" — The job did not complete. Check errorMessage on the job for details and resubmit if appropriate.

Bill of Materials

Retrieve bills of materials and their component breakdowns.

GET /bom/{id}

Get bom by ID

Parameters

NameInTypeRequiredDescription
id path string<uuid> required
related query string[] optional Include related entities. Omit for all, "false" for none, or specify field names (repeatable, supports dot notation)

Responses

200
200 Response
FieldTypeDescription
data BomDto
id* string Unique identifier for the bill of materials record
versionLabel* string Per-lineage version label (MAJOR.MINOR), e.g., `v1.0`. MAJOR bumps on structural change (new AI generation, scenario fork, scenario promotion); MINOR bumps on revision (DLN-151). Assigned by the orchestrator at write time based on the most-recent prior BOM in the same `(productId, scenarioId)` lineage.
sourceType* string How this BOM came to exist. `ai_generation` is the initial BOM produced by the AI Step 3 Create-path (DLN-135). `ai_revision` is a diff applied by DLN-151. `import` is loaded from an external source. `human` is manually created. `scenario_fork` branches off an actual BOM into a scenario lineage (DLN-159). `scenario_promotion` is a scenario BOM promoted into actual lineage (DLN-280). The vocabulary is system-owned and small, so an enum constraint is appropriate (contrast `BOM_COMPONENT.type`, which is relaxed to allow orthogonal flag semantics).
enum: ai_generation, ai_revision, import, human, scenario_fork, scenario_promotion
scenarioId string Lineage discriminator. NULL signals the actual lineage; a non-NULL UUID identifies a scenario lineage. The Scenario entity is introduced by DLN-159; until then this is a plain UUID column (`scenario_id`) with no FK constraint. DLN-135 always writes NULL.
promotedFromBomId string When `sourceType = scenario_promotion`, the scenario BOM that was promoted into actual lineage. NULL otherwise. Plain UUID column (`promoted_from_bom_id`) with no FK constraint — same convention as `priorBomId` and the legacy `parentBomId`. Populated by DLN-280.
generationDate* string Date and time when the BOM was generated
status string Status of the BOM generation process (e.g., "pending", "completed", "failed")
enum: pending, completed, failed
components BomComponentDto[] List of components and their costs that make up the product - accepts array of full objects, id strings, or id numbers
id* string Unique identifier for the BOM component
priorBomComponentId string Optional predecessor BOM_COMPONENT row in the same temporal lineage. Set by revision flows (DLN-151) when this component evolved from a prior version. NULL for components on a newly generated BOM. Stored as a plain UUID column (`prior_bom_component_id`) with no FK constraint — same convention as `productId` / the existing `parentBomId` on BOM. Lineage integrity is enforced at the application layer.
bom any The parent BOM - accepts full object, id string, or id number
name* string Name of the component in the bill of materials
type* string Materiality classification for the component. One of: `sub-assembly` (has a populated `product` object and multi-entry `materials[]`), `direct-material` (raw input consumed into the finished product; no `product` object), `indirect-material` (packaging, consumable, or waste; paired with exactly one of `isPackaging` / `isConsumable` / `isWaste` set to true). Type and the `isPackaging`/`isConsumable`/`isWaste`/`isByproduct` flags are orthogonal axes: type answers materiality, flags answer cost treatment. DLN-286: enum-enforced at schema + DTO. The cost-analysis job normalizes the legacy value (`raw material`) before write on both create and revision paths; existing rows with non-canonical values stay readable (the DB CHECK add is warn-only via cruddy_constraint_warnings) but must be normalized before update.
enum: sub-assembly, direct-material, indirect-material
classification string DLN-286 (FR-R1): refresh classification — how the Data Refresh prices this node and (Phase 2) whether it recurses. Derived by the cost-analysis job from `type` + `makeVsBuy` and persisted: direct/indirect-material → `commodity`; sub-assembly + MAKE → `manufactured-sub-assembly`; sub-assembly + BUY/UNKNOWN/absent → `bought-sub-assembly` (conservative default — UNKNOWN never opts a node into recursion). A stored value is never overwritten by re-derivation.
enum: commodity, manufactured-sub-assembly, bought-sub-assembly
catalogEntityId string FK to CATALOG_COMMODITY.id or CATALOG_SUB_ASSEMBLY.id (see catalogEntityType). Plain uuid, not a validated schema relation — deferred-FK convention (matches rateSnapshotId on COST_ANALYSIS_COMPONENT_DRIVER): the two catalog tables are a discriminated union, so no single relation target applies. Set by the refresh pipeline (DLN-289) on first successful identity-match; reused on subsequent refreshes so a component never re-resolves identity every run.
catalogEntityType string Which catalog table catalogEntityId points into, when set.
enum: catalog-commodity, catalog-sub-assembly
cost* number Extended line total for this component in minor currency units (e.g., cents) — quantity is already applied; never multiply by quantity again. Per-unit prices live in estimatedCostPerUnit / referenceCostPerUnit / supplierCostPerUnit.
presented any The line cost exactly as a user typed it in a reporting currency other than USD, kept beside `cost` (that amount converted to USD at the analysis's fxRate) so it reads back as typed. Written only by a component correction; absent on every other line, which a reader presents at the analysis's rate. A reader uses it only while `currency` is the analysis's reportingCurrency and the amount still converts to `cost`, so a later change to `cost` that carried no new twin falls back to the rate.
quantity* number Total quantity of this component consumed for the BOM's whole reference batch (refQtyValue of refQtyUnit), on the same basis as `cost`, so cost ≈ estimatedCostPerUnit × quantity. Not a per-unit-of-output figure (DLN-945). Fractional by design (DLN-934): a component pro-rated across the batch, such as a drum, IBC or sack larger than the batch, genuinely has a quantity below 1. Typed `integer`, such a value was floored on the way in, sometimes to 0, while `cost` stayed correct, so the line contradicted itself. Matches the raw-material entry, which has always been `number`.
unitType* string Unit of measurement for the component (e.g., piece, kilogram, liter)
estimatedGramsPerUnit number Estimated grams of this component per unit.
estimatedCostPerUnit number Estimated cost in minor currency units (e.g., cents) of this component per unit.
estimatedCostPerUnitStdErr number Approximation of the standard error of the estimated cost in minor currency units (e.g., cents) of this component per unit.
referenceCostPerUnit number DLN-377: universal list / spot / OEM reference price per unit, in minor currency units (cents) — the undiscounted commodity price BOM creation produces. The auditable baseline; never overwritten by downstream adjustments.
supplierCostPerUnit number DLN-377: the price this manufacturer/supplier actually faces per unit, in minor currency units (cents). Equals referenceCostPerUnit until a supplier-specific pricing mechanism computes it (factor-1 default).
supplierDiscountFactor number Scale-based discount factor applied to referenceCostPerUnit to produce supplierCostPerUnit (0 < factor <= 1; 1 = no discount). Computed by the supplier purchasing-power step from the buyer's annual commodity / category spend through a capped, saturating curve. Null when no discount has been computed.
supplierDiscountBasis string Spend basis behind the factor: commodity_scale for raw materials (buyer's spend on the commodity itself), supplier_leverage for bought whole units (buyer's spend on the part category; lower ceiling — only margin and conversion are negotiable).
enum: commodity_scale, supplier_leverage
supplierDiscountSource string Provenance of the financials scale signal the factor was derived from, from filed statements down to structural proxies.
enum: edgar_financials, reported_financials, ai_estimate, employees_proxy, supplier_tier, naics_default
supplierDiscountConfidence string Confidence in the discount, combining the scale-signal quality and the commodity-share source (AI-estimated shares cap at medium).
enum: high, medium, low
referencePriceSource string Which number referenceCostPerUnit actually holds. `ai_estimate` is the estimate BOM creation produced. `customs_anchor` is the customs-derived import unit value published by the price-reference feed. `catalog_price` is a price sourced outside both: the price the catalog carries for this component's matched entity, which a price refresh either wrote from its own market lookup or preserved because an account or an external database had set it. The label names the price that is served — the pipeline records `customs_anchor` only where referenceCostPerUnit is the anchor held in referencePriceComparison. Absent means the reference has not been priced by a pass that records provenance, which is a different fact from `ai_estimate`; nothing backfills a label onto a component no pricing pass has seen. This names the number served, NOT how the component was priced — referencePriceMethod answers that, and the two come apart whenever a refresh replaces the served number without retracting the pricing decision behind it.
enum: ai_estimate, customs_anchor, catalog_price
referencePriceMethod string How this component's reference price was arrived at. `anchor` means the customs-derived import unit value priced it, which is the ordinary case wherever an eligible anchor exists. `ai_fallback` means no eligible anchor was found and the AI estimate priced it instead. The estimate is computed either way and serves as a cross-check, so a disagreement lowers referencePriceConfidence and is recorded in referencePriceReconciliation rather than changing the number. Distinct from referencePriceSource, which names the number actually served. Four pairings are legal and two are refused: a component priced from the anchor cannot serve the estimate, and one that fell back to the estimate cannot serve the anchor. A refresh that serves the anchor records `customs_anchor` as the source and `anchor` here. A refresh that replaces the served price from the catalog records `catalog_price` as the source and leaves this field alone, because how the pipeline priced the component is not retracted by a later price arriving from somewhere else. Absent means no pricing pass has decided for this component, which is a distinct fact from either value. `ai_fallback` appears on legacy rows only: the pipeline no longer writes a label on a component that fell back, so a current fallback leaves this field absent.
enum: anchor, ai_fallback
referencePriceReconciliation string What the estimate-against-anchor cross-check concluded. The first three are verdicts on an anchor that priced the component and had an estimate beside it: corroborated, or off by a minor or a major margin. A major mismatch routes the component to review; neither mismatch changes the price. `no_cross_check` is an anchor that priced the component with no usable estimate standing against it — unreconciled, not disagreeing. `ai_fallback` is the case where no anchor priced it at all. The vocabulary partitions referencePriceMethod rather than crossing it: the first four occur only with method `anchor`, and `ai_fallback` occurs only with method `ai_fallback`. Stored as its own column rather than derived so a reader — and the warehouse — can count outcomes without joining to the comparison; the agreement with the method is enforced on write, because a field that can drift will. Absent means no cross-check pass has run. `ai_fallback` appears on legacy rows only; the pipeline no longer writes it.
enum: anchor_corroborated, anchor_mismatch_minor, anchor_mismatch_major, no_cross_check, ai_fallback
referencePriceOriginCountry string Component countryOfOrigin (ISO alpha-2) at pricing time. Together with referencePriceDestinationCountry this snapshots the requested country context, including on AI fallback, where it is the only reference-price field written: it records where the pass looked and claims no price. An unmapped origin is omitted from the resolver call; referencePriceMatchedScope records what actually matched.
referencePriceDestinationCountry string Analysis importCountry (ISO alpha-2, US by default) at pricing time. The resolver receives its alpha-3 equivalent. An unmapped destination falls back to AI instead of silently selecting another market.
referencePriceMatchedScope string Which served price priced this component: the exact origin lane, the destination market row, or the worldwide HS6 row. Written when a customs price serves, and required then. `none` is the lookup's own answer for a code it served nothing for; the pipeline stores it beside the country context on a component that fell back for that reason. It claims no price, so clearing a stale label leaves it in place. Absent where no lookup answered, or where its row could not price the component.
enum: lane, market, default_hs6, none
referencePriceEditionId string The price-reference edition consulted for this component, named by the feed's own edition identifier. Editions are retained, so this is what lets a stored analysis be replayed against the prices it saw. Set only where a customs price served this component; a lookup that served nothing writes no edition, and the per-run coverage log carries that outcome instead. The extraction timestamp is not copied here — it is a property of the edition this identifier already names. Bounded because the field reaches the API as a client-supplied string like any other: an edition id the producer mints runs to a few dozen characters, and nothing about the column stops an arbitrarily large one being stored under its name.
referencePriceConfidence number How much to trust referenceCostPerUnit, 0 (low) to 100 (high), scored from the anchor's own quality and from how well the estimate corroborates it. Distinct from confidence.cost, which is the estimator's confidence in its own number: this score is evidence from outside it, so a price no anchor covers can carry a low score with no comparison stored at all. Product states one band boundary: High is 75 and above. The Medium/Low split is the scoring pass's to set and is not yet stated, so no boundary for it is published here. A component priced by the AI fallback scores below High by construction. Whole units of a hundred rather than a fraction, so the stored value has one exact representation and sorts and aggregates in the warehouse without a float comparison.
referencePriceScoredAgainst number The referenceCostPerUnit that referencePriceConfidence was taken against, in minor currency units (cents). The served reference can move after a score is written — a price refresh replaces it, and a judge correction re-aligns it to a corrected estimate — and a score that outlives its subject reads as current when it is not. Holding the number scored makes that visible: where this differs from referenceCostPerUnit, the score describes a price the component no longer serves.
referencePriceComparison any What the customs anchor said about this component's reference price, and the arithmetic behind the score. Held so a reviewer can redo the comparison from the row alone instead of re-reading the edition, and so the record of anchor-versus-estimate accumulates while the estimate is still the number served. Absent where the feed covered nothing for this component, or was not consulted.
referencePriceChoice any Which of the customs anchor (the basket price) and the AI estimate this line serves, why, and how far apart they are. A refresh records it too, once the round has priced the line. The anchor serves only a bulk-grade material line, and only while it is between half and double the estimate, bounds included; every other line keeps the estimate. Written by the pricing pass on every line where an anchor could be brought to the line's unit, whatever the line's grade, so both prices are kept whichever one serves. Stored beside referencePriceReconciliation rather than replacing it: that flag is the cross-check's verdict on a served anchor, this is the choice between the two numbers. Internal evidence; no screen shows it. Written by the last pass that weighed an anchor, and never left stale: a judge correction that moves the price clears it, and so does a later pass that finds no usable anchor or a price set by hand. An anchor an earlier pass served that the rule now refuses goes back to the estimate, and the record says why. Absent where no anchor could price the line, where a price set by hand outranked it, or where the line's quantity held its prices.
countryOfOrigin string Country where the component is most likely manufactured (two digit ISO country code, e.g., US, CN).
makeVsBuy string Whether the product's manufacturer makes this component in-house (`MAKE`) or buys it from an upstream supplier (`BUY`). `UNKNOWN` when it cannot be determined — do not guess. Classification only: this is an explicit, auditable input signal and drives no cost adjustment here. The implicit default mapping is sub-assembly→`BUY`, direct-material→`MAKE`; orthogonal to `type`.
enum: MAKE, BUY, UNKNOWN
buyDetails any Upstream-sourcing detail for `BUY` components. Omit for `MAKE`/`UNKNOWN`. The supplier here is who the part is purchased from, which is distinct from the sub-assembly `product.manufacturer` (who made it) even when they coincide.
htsusCode string The Harmonized Tariff Schedule of the United States line this component classifies to — the component's own code, distinct from the nested `product.htsusCode`, which describes a referenced product identity and exists only on sub-assemblies. Written as the schedule publishes it: dotted, ten digits for a statistical line and eight for a subheading the schedule gives no statistical breakout. The pattern is the same one `product.identifiers.htsusCode` carries, and it is the write boundary: the first six digits are the subheading commodity price references are keyed on, and a value that is not a dotted code would still yield six leading characters — a join key made of something that is not a code. Null means no code. The three HTS fields form one state at every write boundary: a code requires a non-`unresolved` `htsusSource`; `unresolved` forbids both code and taxonomy version; clearing a code clears the other two columns rather than leaving stale provenance behind.
htsusSource string Where `htsusCode` came from, and — when there is none — why. This is the discriminator for the component's tariff state. `deterministic`: picked from the classification corpus; the BOM service still confirms the exact line in the published schedule (usitc_hts_10digit) before accepting it. `ai_estimate`: free-hand model output — the same token `supplierDiscountSource` uses on this row for the same kind of value. `user`: a person's explicit pick, which outranks re-derivation. `unresolved`: classification ran and produced nothing the schedule confirms — a gap, and therefore a no-code state. Absent means classification has not run for this component. A user or AI pin the reference does not publish may keep its code for review but has no `htsusTaxonomyVersion` and exposes no commodity-price key.
enum: deterministic, ai_estimate, user, unresolved
htsusTaxonomyVersion string Server-owned revision of the published schedule the code was confirmed against — the `version` the exact matching `usitc_hts_10digit` row carries, e.g. `2026HTSRev10`. A caller may echo a value it read, but the BOM service accepts it only when a fresh reference lookup returns that same revision and always writes the lookup result. Recorded so a code stays reproducible across a schedule revision: the same component reclassified after a revision can legitimately land elsewhere, and without the revision the two answers are indistinguishable from a defect. Null when `htsusCode` is null or the exact line is unpublished; changing or clearing a code clears the old revision before any new one is stamped.
beaCode string BEA IO commodity code identifying what this component is, as a commodity — the lookup key into the BEA×NAICS composition matrix (bea_composition dataset). Chosen from the controlled bea_commodities vocabulary at BOM generation and enforced against it before persistence: a code outside the vocabulary is nulled with beaUnmappedReason set. Null means unmapped; downstream composition lookups fall back to estimates. Sub-assemblies carry the commodity category of the purchased article, not their dominant raw material.
beaConfidence string Model-reported confidence in the beaCode mapping. Null when the component is unmapped.
enum: high, medium, low
beaUnmappedReason string Why beaCode is null — either the model found no vocabulary entry that covers the component, or a proposed code was rejected as outside the vocabulary. Null when beaCode is set.
isPackaging boolean Indicates if the component is packaging material used to contain, protect, or present the finished product (e.g., cartons, labels, shrink wrap). Packaging contributes to Indirect Materials with a positive cost.
isWaste boolean Indicates if the component is scrap or non-valuable residue generated during production that must be disposed of. Waste has its own cost category (positive price and cost). If a byproduct would require payment to dispose of, classify it as waste, not a byproduct.
isConsumable boolean Indicates if the component is an indirect process consumable used during manufacturing that does not end up in the final product (e.g., sterilization agents, cleaning solvents, process gases, water). Consumables contribute to Indirect Materials with a positive cost.
isByproduct boolean Indicates if the component is a valuable co-product of the manufacturing process that generates revenue or offsets cost (e.g., a chemical intermediate sold to another industry). Byproducts carry a positive price but a negative cost representing the credit received. Direct Materials stays gross; the credits come off the analysis's total as its Byproduct Credits bucket. If the component instead requires payment to dispose of, classify it as isWaste, not isByproduct.
materialGrade string The grade of material this line buys, set on every direct-material and indirect-material line. `bulk`: sold as a commodity — resins, metals, basic chemicals, paper, standard packaging. `special`: pharma, medical, high-purity, electronic, reagent or enzyme grades. `not_a_purchase`: scrap, waste, by-products, anything made on site; a line flagged isWaste or isByproduct is always `not_a_purchase`. Null on sub-assemblies, which are bought parts, and on lines from analyses made before the grade was recorded. A line with no grade never takes a basket price. A revision keeps a line's grade unless the line itself changes.
enum: bulk, special, not_a_purchase
sources string[] List of URIs to sources or references for the component information. This can be spec sheets, manufacturer websites, datasheets, etc.
confidence any Detailed confidence information about various aspects of the component data
justification string Explanation of the reasoning behind all of the mappings made in this component (e.g., sources, justification for assumptions, confidence, etc.). Keep this to less than 5000 characters. Can also include other considerations about this product.
materials RawMaterialEntryDto[] Estimated raw materials that make up this component. In the case that this component is just a single @raw-material-entry, this is just the one raw material itself in the array. In the case of a sub-assembly, this is the list of @raw-material-entries that make up the sub-assembly. - accepts array of full objects, id strings, or id numbers
productId string Optional reference to PRODUCT when this component represents a known sub-product. Cross-service reference — Cruddy does not emit a FK constraint (DLN-168 forbids cross-service FKs). Populated by recursive sub-product resolution (DLN-154); NULL otherwise.
product any Information about the @product that the component is, when the component is a sub-assembly. Every component of type "sub-assembly" MUST populate this object; omit it only when the component is a raw material. If the manufacturer of the sub-assembly is unknown or the sub-assembly is a commodity, still populate the object and set the manufacturer to GENERIC — an unidentified manufacturer is not a reason to leave the object out, nor to reclassify a genuine sub-assembly as a raw material.
refQtyValue number Reference quantity of output product this BOM represents. Examples: 1 for finished goods (paired with `refQtyUnit: "piece"`); 1, 100, 1000 for chemicals/commodities (paired with `refQtyUnit: "kilogram"` or similar). Per-component `cost`/`quantity` values are calibrated against this reference batch.
refQtyUnit string Unit of measurement for `refQtyValue` (e.g., `kilogram`, `piece`, `liter`).
assumptions string[] Critical assumptions about process, material grade, formulation, yield, or other factors that influenced the BOM composition and component costs. Examples: assumed reaction route, catalyst choice, solvent grade, food-grade vs industrial-grade materials, assembly method, raw material form factor.
processParams any Manufacturing process classification for cost modeling. Used to apply yield, compliance, and setup adjustments in cost analysis.
productId string ID of the product this BOM was generated for. Enables direct querying of all BOMs for a given product.
priorBomId string Predecessor BOM in the same `(productId, scenarioId)` lineage. NULL on the first BOM in a lineage; set on every successor (generation, revision, scenario fork, scenario promotion). Establishes per-lineage chaining. Plain UUID column (`prior_bom_id`) with no FK constraint — same convention as the legacy `parentBomId` field this replaces.
revisionDiff object The structured diff that was applied to the parent BOM to produce this revision. Stored for auditability and lineage tracking. Contains operations (modify/add/remove), updatedAssumptions, and a summary of changes. Stored as opaque JSON.
meta object
404 Not found

Company

Look up company profiles and related information.

GET /company/{id}

Get company by ID

Parameters

NameInTypeRequiredDescription
id path string<uuid> required
related query string[] optional Include related entities. Omit for all, "false" for none, or specify field names (repeatable, supports dot notation)

Responses

200
200 Response
FieldTypeDescription
data CompanyDto
id string Unique identifier for the company record
name* string Legal or commonly known name of the company
nameNormalized string Deterministic normalization of `name` used for per-tenant deduplication (lowercase + trim + collapse-whitespace + strip-punctuation + strip common corporate suffixes + Unicode NFKC). Service-computed on write; not client-supplied. Indexed per-tenant by a partial unique index installed at service bootstrap. See DLN-133.
accountId string FK to the account that owns this company record. When null, the company is considered a public/shared record.
legalStructure string Legal entity type (e.g., LLC, Inc, GmbH, Pty Ltd)
identifiers any Official business registration identifiers as key-value pairs. Values are the corresponding registration numbers. The well-known keys are declared below; other identifier types are still accepted.
country string Country of incorporation (ISO 3166-1 alpha-2 code, e.g., US, CA, GB, DE)
naicsIndustries string[] Two-digit NAICS sector codes describing the company's primary industries
naics string[] Six-digit NAICS codes describing the company's specific lines of business
founded string Date the company was founded or incorporated
domains string[] Company domain names (e.g., google.com, abc.xyz)
website string Primary corporate website URL
status string Current operational status of the company
enum: active, dissolved, merged, acquired
headquarters any Primary headquarters address - accepts full object, id string, or id number
employees number Approximate total number of employees
aliases string[] Alternative or former names the company is known by (e.g., trade names, DBAs, previous names)
logos LogosDto[] Company logos, past and present, with usage dates
logo* string URI to the logo image file (full GCS path)
status string Whether this logo is currently in use
enum: active, inactive
description string Label or context for this logo (e.g., primary, icon, wordmark)
start_date string Date this logo started being used
end_date string Date this logo stopped being used, if inactive
socials SocialsDto[] Social media profiles associated with the company
type* string Social media platform (e.g., linkedin, twitter, facebook)
url string Full URL to the company's profile on this platform
username string Company's username or handle on this platform
addresses AddressesDto[] Additional company locations beyond the headquarters (e.g., offices, branches, factories)
type* string Location type (e.g., office, branch, factory, warehouse)
address* any Physical address of this location - accepts full object, id string, or id number
ticker string Stock ticker symbol for a publicly-traded company (e.g., AAPL, BRK.A). Used to resolve the company's SEC CIK for EDGAR financial-statement lookups.
cik string SEC Central Index Key, 10-digit zero-padded. Populated on first successful EDGAR resolution for a US public filer.
financials CompanyFinancialsDto[] Annual financial snapshots (revenue, COGS, SGA components) for this company, one per fiscal year. Sourced from EDGAR filings or operator entry. See DLN-73. - accepts array of full objects, id strings, or id numbers
id string Unique identifier for the financials record
company any The parent company this snapshot describes - accepts full object, id string, or id number
fiscalYear* number Fiscal year covered by this snapshot
periodStart* string Start date of the reporting period (e.g., 2024-01-01)
periodEnd* string End date of the reporting period. Used by the cross-taxonomy validity guard to reject stale observations where values lag revenue's period by more than ~1 year.
statementType* string Filing type the snapshot was extracted from, or "manual" when entered by an operator.
enum: 10-K, 20-F, manual
currency* string ISO 4217 currency code (e.g., USD, JPY) for all numeric amounts in this row. Values are stored as-reported in this currency.
revenue* number Annual revenue, as reported in the filing, in units of `currency`.
cogs number Annual cost of goods sold, as reported in the filing, in units of `currency`. Null when the filing yields no COGS figure or an operator did not know it. Never 0 for "unknown".
salesAndMarketing number Sales & Marketing expense component of "our SGA" (sum of S&M + G&A + R&D, deliberately excluding non-operational items). Nullable when the filer does not break it out.
generalAndAdministrative number General & Administrative expense component of "our SGA". Nullable when the filer does not break it out.
researchAndDevelopment number Research & Development expense component of "our SGA". Nullable when the filer does not break it out.
sellingGeneralAndAdministrative number The combined "Selling, general and administrative" line as filed (us-gaap:SellingGeneralAndAdministrativeExpense), for filers that report one line instead of separate S&M and G&A components. Null when the filer breaks the components out, or files neither. Kept distinct from the components so a consumer can tell a lumped figure from a summed one (DLN-884).
operatingIncome number Operating income as reported (us-gaap:OperatingIncomeLoss). Signed — a loss is negative, unlike every other figure on this row. Recorded as drift-detection evidence only: the cost-analysis pipeline derives its own operating margin from revenue, COGS and SG&A and does not read this figure (DLN-913), because we cannot know what the filer included when they calculated it. Nullable.
filingDate string SEC filing/accepted date of the underlying 10-K or 20-F. Drives the 300-day EDGAR refresh check. Null for manual rows.
accessionNumber string EDGAR accession number (e.g., 0000008670-24-000011) that sourced this row, for audit and per-run traceability. Null for manual rows.
retrievedAt string Timestamp at which this row was written (EDGAR fetch or manual entry).
lastCheckedAt string Last time EDGAR was probed for this row. Combined with a 30-day negative-cache window to prevent thrashing when filingDate has passed the 300-day fresh window but no newer filing exists.
source* string Provenance of this row. Only "edgar" and "manual" rows are persisted. LLM fallback responses are never persisted (returned directly to caller with source="llm" in the response envelope).
enum: edgar, manual
enteredBy string Operator who entered this row. Required when source="manual"; null for "edgar".
reason string Operator-supplied justification for a manual entry (e.g., "EDGAR extraction failed; numbers from investor presentation"). Required when source="manual".
basis string Whose figures these are, as a value to branch on rather than a sentence to parse. "own" - the company was bound to its own SEC registrant. "parentConsolidated" - it was bound through a subsidiary relationship, so revenue and costs are the PARENT registrant's consolidated filings and describe a larger entity than this company; treat them as qualified rather than exact. Null means no basis is claimed, not that the basis is "own": manual rows carry figures an operator typed in, for which this service asserts nothing, and an EDGAR row can also arrive here when the evidence needed to decide was unavailable. Read `source` to tell those apart - a null basis on source="manual" means no claim was made, and on source="edgar" it means the evidence needed to decide could not be read.
enum: own, parentConsolidated
basisNote string Human-readable companion to `basis`, naming the parent registrant when basis="parentConsolidated". Display text only - branch on `basis` and read the parent off `basisRegistrantCik` / `basisRegistrantName`, never off this string. Null when there is nothing to qualify.
basisRegistrantCik string The SEC registrant whose filings these figures came from, when basis="parentConsolidated" - the machine-readable form of the entity `basisNote` names in prose. Both are built from the same resolution, so they cannot disagree. Null unless there is a parent to name.
basisRegistrantName string The registrant title behind `basisRegistrantCik`, with EDGAR's disambiguation markers stripped ("INTERNATIONAL PAPER CO", not "INTERNATIONAL PAPER CO /NEW/"). Display or match on it; the CIK is the identifier. Null unless there is a parent to name.
identityRoute string Which evidence selected the SEC registrant these figures were read from, as a value to branch on. `basis` answers whose figures these are, and deliberately does not answer this: a bind can be the company's own registrant on every reading and still have been SELECTED by weaker evidence than the row beside it. "suppliedIdentifier" - the CIK or ticker already on the company record, set by an operator or persisted by an earlier resolution. "nameMatch" - the company's own name is EQUAL to exactly one registrant's current or former title once both are normalized. "alias" - a row in the alias table vouched for the CIK (an EDGAR former-name or Exhibit 21 record, or an operator assertion). "researchedTicker" - a researched ticker symbol selected the registrant and that registrant's own name corroborated it, but nothing stronger did; the weakest route, and the only one whose CIK is deliberately never written back to the company record, so it is re-earned on every fetch rather than frozen. Null on manual rows, which no resolution bound, and on rows written before this field existed - not a synonym for any route. `reason` carries the prose companion on a "researchedTicker" row.
enum: suppliedIdentifier, nameMatch, alias, researchedTicker
commodityDiscounts CompanyCommodityDiscountDto[] Cached supplier purchasing-power discount factors for this company, one per (commodityCategory, fiscalYear). Written by the cost-analysis supplier-power step; a reusable derived value, recomputed when the underlying financials change. - accepts array of full objects, id strings, or id numbers
id string Unique identifier for the cached factor row
company any The company whose purchasing power the factor describes - accepts full object, id string, or id number
commodityCategory* string Commodity the factor applies to — the BEA IO commodity code carried on the discounted BOM components (the composition-matrix lookup key).
fiscalYear* number Fiscal year of the financials the factor was derived from.
factor* number Discount factor (0 < factor <= 1; 1 = no discount) applied to a reference price to produce the supplier-facing price.
basis* string Spend basis the factor was computed on.
enum: commodity_scale, supplier_leverage
source* string Provenance of the financials scale signal.
enum: edgar_financials, reported_financials, ai_estimate, employees_proxy, supplier_tier, naics_default
confidence* string Confidence in the factor.
enum: high, medium, low
spendUsd number The annual spend estimate (USD) that produced the factor, kept for auditability of the curve input.
computedAt string When the factor was last computed.
metadata object Additional metadata about the company as key-value pairs
meta object
404 Not found

Product

Look up product profiles and related information.

GET /product/{id}

Get product by ID

Parameters

NameInTypeRequiredDescription
id path string<uuid> required
related query string[] optional Include related entities. Omit for all, "false" for none, or specify field names (repeatable, supports dot notation)

Responses

200
200 Response
FieldTypeDescription
data ProductDto1
id* string Unique identifier for the product
name* string Name of the product
description string Detailed description of the product
retailPrice number Approximate, current price of the product, in minor currency units (e.g., cents)
retailPriceCurrency string Currency code for the retail price (e.g., USD, EUR)
unitType string Unit of measurement for the @product (e.g., piece, kilogram, liter)
enum: piece, kilogram, gram, liter, milliliter, meter, foot, square meter, square foot, cubic meter, gallon, pound, ounce, ton, sheet, roll, barrel, board foot
status string Lifecycle status of the product
enum: in development, active, end-of-life
commoditized boolean Indicates if the product is classified as a commodity - very little differentiation between competitors
rawMaterial boolean Indicates if the product is considered a raw material used in manufacturing or production (e.g., steel, plastic, lumber)
parentProductId string FK to the parent @product, if applicable
categoryId string DEPRECATED (DLN-793), read-only compatibility for associations stored before category classification moved to identifiers.unspscCode. New writes and categoryId filters are rejected. Keep this field readable until scripts/dln-793-audit-product-category-removal.sql has passed in every live environment and an explicit follow-up migration removes it.
countryOfOrigin string Country where the product is manufactured (ISO 3166 alpha-2 code, e.g., US, CN)
weightGrams number Product weight in grams
leadTimeDays number Typical lead time in days
minimumOrderQuantity number Minimum order quantity
negotiationUnit any The unit the buyer negotiates in and IMPACT analyses (ADR 0156): a name, such as "pack of 12" or "10 kg tote", and how many base units it holds. The base unit is the one the product's transactions are normalised to (its roll-up's baseUnit). Absent means one base unit. IMPACT's price, should-cost, gap and quantity are all expressed in it; SIGNAL does not use it. Send null to go back to one base unit.
metadata object Additional account-specific metadata about the product
identifiers any Various identifiers associated with the product
urls any Various URLs associated with the product
productKey string For a product made by grouping a transaction import's lines (ADR 0150): the product key its lines share, sha256 hex (ADR 0145). A later import's lines with the same key attach to this product. Null on a product made any other way, and on a NOT_GROUPED one, which no line can join. One live product per key per organisation, by a unique index installed at bootstrap. Set by the server, never by a request.
productKeyVersion string The version of the product key productKey was computed under. Null when productKey is.
productKeyValues any The normalized values that define a grouped product, as the product key computed them: what its lines have in common. Null when productKey is, except on a product split out of a group in review (ADR 0157), which keeps the values of the group it came from.
groupingRung string For a product made by grouping a transaction import's lines (ADR 0150): the rung of the matching ladder that grouped them. MATERIAL_NUMBER and MANUFACTURER_PART_NUMBER are the rungs grouping uses today. NOT_GROUPED is one line that named neither a material number nor a manufacturer part number: it is its own product, and nothing joins it. Null on a product made any other way.
enum: MATERIAL_NUMBER, MANUFACTURER_PART_NUMBER, SUPPLIER_PART_NUMBER, DESCRIPTION, NOT_GROUPED
sourceImportId string The transaction import whose grouping made this product. A bare uuid: the pipeline database is separate. Null on a product made any other way. A product the grouping made is deleted when regrouping or deleting that import leaves it holding no line.
meta object
404 Not found

Pipeline

Submit asynchronous analysis jobs such as cost-analysis pipelines.

POST /pipeline/cost-analysis

Submit a cost analysis pipeline request

Parameters

NameInTypeRequiredDescription
x-app-user-id header string optional Gateway-injected app-user id of the acting user; persisted as the job's submittedById (DLN-296). Callers do not set this — the gateway strips inbound copies and supplies it from the JWT.
x-firebase-uid header string optional Gateway-set Firebase UID of the calling user (Langfuse user attribution). Server-injected — clients never supply it; the gateway strips inbound copies.
x-attributed-app-user-id header string optional Internal service-to-service attribution: the app user a service call acts on behalf of (e.g. the admin who approved an iteration). Honored ONLY for service-classified callers, and stripped from inbound requests by the gateway.
x-feature-flags header string optional Gateway-resolved feature flags for this (organization, user). Server-injected — clients never supply it; the gateway strips inbound copies. With TRANSACTIONS, a run for an existing productId whose roll-up has purchases is priced from it in the product's negotiation unit: quantity 1, pricesPaid the average price per negotiation unit, and the analysis reports in the purchases' currency.

Request Body

CostAnalysisRequestDto
FieldTypeDescription
id string Unique identifier for the pipeline request.
caVersion string The version of cost analysis generation process to use.
bomVersion string The version of bill of materials to use.
productName string Name of the product. Required for a normal run; for an iteration (previousAnalysisId set) it is derived from the prior run, so it may be omitted (DLN-197/ADR 0073). A new_analysis run (feedbackId without previousAnalysisId, DLN-196) is a normal run, so it must supply this.
manufacturerName string Name of the manufacturer. Required for a normal run; for an iteration (previousAnalysisId set) it is derived from the prior run, so it may be omitted (DLN-197/ADR 0073). A new_analysis run (feedbackId without previousAnalysisId, DLN-196) must supply this.
productId string ID of an existing product entity. When provided along with manufacturerId, skips AI company/product resolution and uses the existing entities.
manufacturerId string ID of an existing manufacturer entity. When provided along with productId, skips AI company/product resolution and uses the existing entities.
supplierNames string[] Supplier name(s). Each entry is resolved via a sequential AI company-resolution query, so the list is capped at 10.
supplierIds string[] IDs of existing supplier entities. When provided, skips AI resolution for supplierNames and links these existing suppliers directly.
productWebsite string Website of the product.
manufacturerWebsite string Website of the manufacturer.
productContext string Context about the product.
packagingDetails string The product's packaging, as the user typed it when promoting a SIGNAL product (ADR 0159). The BOM prompts read it after productContext.
specifications string The product's specifications, as the user typed them when promoting a SIGNAL product (ADR 0159). The BOM prompts read them after productContext.
processDescription string How the product is made, as the user described it when promoting a SIGNAL product (ADR 0159). The BOM prompts read it after productContext.
naicsCode string The product's industry as a six-digit NAICS code, chosen by the user. Outranks both the AI-resolved code and deterministic NAICS classification: the product stores it with naicsSource 'user', which classification leaves alone. A code from the prior NAICS revision is translated to its successor; a code that names no industry in either revision is refused.
pattern: ^\d{6}$
signalItemId string<uuid> The Signal item this analysis was started from ("Impact: Analyze this", DLN-1125). An item not found in the caller's tenant is refused with 404. When the item's HTS code is confirmed, the product stores that code with htsusSource 'signal' and the run derives none of its own; an unconfirmed or missing code changes nothing. Ignored on an iteration.
uuid
manufacturerContext string Context about the manufacturer.
supplierContext string Context about the supplier(s).
purchaserContext string Context about the purchaser.
countryOfOrigin string ISO 3166-1 alpha-2 country code for the product's country of origin.
pattern: ^[A-Z]{2}$
importCountry string ISO 3166-1 alpha-2 country code where the product is imported to.
pattern: ^[A-Z]{2}$default: "US"
reportingCurrency string ISO 4217 code of the currency the analysis is reported in; defaults to USD when absent. The engine computes in USD and converts the finished result at the stored annual rate; the USD figures are kept alongside. Prices paid are entered in this currency. A code with no rate for the run year fails the run.
pattern: ^[A-Z]{3}$
annualVolume number Expected annual purchase volume of the finished product.
annualVolumeUnit string Unit for annualVolume (e.g., kg, liter, unit).
unitType string Base unit of measurement for the product (e.g., kilogram, piece, liter). Overrides AI inference when provided.
enum: piece, kilogram, gram, liter, milliliter, meter, foot, square meter, square foot, cubic meter, gallon, pound, ounce, ton, sheet, roll, barrel, board foot
countryOfOriginText string Raw, unnormalized country-of-origin text from a bulk import (e.g. 'China', 'P.R.C.'). Unlike countryOfOrigin, it is NOT validated to alpha-2; Product Resolution normalizes it. Used only by the import path — the form/API should send the strict countryOfOrigin.
unitTypeText string Raw, unnormalized unit-of-measure text from a bulk import (e.g. 'kg', 'tonne'). Unlike unitType, it is NOT validated to the closed enum; Product Resolution normalizes it. Used only by the import path — the form/API should send the strict unitType.
quantity number Quantity of the product for this cost analysis (e.g., 2 for 2 tons, 1000 for 1000 units). Decimals supported. Defaults to 1 if not specified.
pricesPaid PricesPaidItemDto[] Historical prices paid, entered in the reporting currency. On a run that reports in a currency other than USD every item must be in that currency; a mismatch is a 400 naming both.
date string Date when the price was paid.
price* number Price paid in minor currency units (e.g., cents).
currency* string ISO 4217 currency code (e.g., USD, EUR). Case and surrounding whitespace are tidied before validation, so a bulk-import cell of 'usd' or 'CAD ' is read as USD or CAD.
pattern: ^[A-Z]{3}$
enableBomJudge boolean Retired (DLN-1074): accepted for backward compatibility and ignored. The BOM Judge step no longer runs.
enableTier1Adjustments boolean If true, apply Tier 1 process parameter adjustments (compliance overhead and batch setup amortization) derived from the BOM process parameters. When false, the ASM industry averages are used without process-level adjustments.
default: false
enableSupplierPower boolean If true, apply the supplier purchasing-power step: components are priced at the supplier-facing cost (reference price x a capped, scale-based discount factor). Set false to keep every component at the universal reference price.
default: true
supplierScaleTier string Optional explicit supplier scale tier, used as the purchasing-power signal when no financials are available (fallback of last resort before no-discount).
enum: enterprise, mid, small
enableProcessAssessment boolean If true, run a process assessment step that generates Tier 2 adjustments.
default: false
costAdjustments CostAdjustmentItemDto[] Explicit named adjustments to apply to cost-analysis components.
name* string Human-readable label for this adjustment.
category* string Cost category type to target.
enum: direct_materials, indirect_materials, labor, overhead
type string How to apply the adjustment.
enum: multiplicative, additivedefault: "multiplicative"
factor number Multiplicative factor (e.g., 1.1 for a 10% increase).
amount number Additive amount in minor currency units (cents).
enableQa boolean If true, run an AI-powered QA evaluation step after cost analysis.
default: false
enableAiTariffResolution boolean If true, run an AI-powered Tariff Resolution step (Step 3d) to determine the effective import tariff rate, replacing the static HTSUS dataset lookup. Supports non-US import countries when enabled.
default: true
annualRevenue number DEPRECATED (DLN-73): Manufacturer annual revenue. Prefer POST /company/{id}/financials with source=manual. Hard-overrides the EDGAR auto-fetch for this run.
annualCogs number DEPRECATED (DLN-73): Manufacturer annual cost of goods sold. Prefer POST /company/{id}/financials with source=manual.
annualSga number DEPRECATED (DLN-73): Manufacturer annual SG&A expenses. Prefer POST /company/{id}/financials with source=manual.
enableMonteCarlo boolean If true, run Monte Carlo simulation to compute costMin/costMax. Bounds derive from the MC P10/P90 but are widened where needed so the deterministic cost always lies within [costMin, costMax]; a widened side is a containment bound, not a strict percentile.
default: true
deterministicClassification any Deterministic code classification toggles, one per taxonomy. NAICS and UNSPSC default per environment (on in development and staging, off in production, via DETERMINISTIC_CLASSIFICATION_DEFAULT_ON on the cost-analysis job) and each key defaults independently, so {"naics": false} leaves UNSPSC at its default. Defaults are documentation only — the job side applies them, in jobs/cost-analysis/lib/options.py.
analysisConfig string Named analysis configuration preset (e.g., "basic").
existingBomId string ID of an existing bill of materials to reuse. When provided, the pipeline skips BOM generation and uses this BOM for downstream steps. If bomContext is also provided, the BOM is revised rather than reused as-is.
bomContext string Free-text instructions for revising an existing BOM. Only used when existingBomId is also provided. Describes what should change from the previous BOM (e.g., "change support legs from 2 to 4 pieces").
previousAnalysisId string DLN-197/ADR 0073: the cost analysis being iterated on (with-rerun). This field — together with feedbackId — is the iteration discriminator: when both are set, the orchestrator resolves the prior run via its computeInputs.sourceJobId, reuses its entities + BOM, and revises the BOM from the feedback corrections (so productName/manufacturerName are derived and may be omitted). Omit it for a normal or new_analysis run.
feedbackId string DLN-197/ADR 0073: the analysis-feedback record this run fulfils. With previousAnalysisId it drives an iteration rerun (the orchestrator reads its normalizedCorrections as the single source of truth for the BOM revision). Without previousAnalysisId it marks a new_analysis run (DLN-196) for audit: the orchestrator stamps this feedback record’s resultAnalysisId with the new CostAnalysis it creates.
includeBomPackaging boolean Include packaging components in the BOM. When false, the AI is instructed to omit packaging from the BOM and packaging costs use ASM industry averages.
default: true
includeBomWaste boolean Include waste components in the BOM. When false, the AI is instructed to omit waste from the BOM and waste removal costs use ASM industry averages.
default: true
includeBomConsumables boolean Include consumable components in the BOM. When false, the AI is instructed to omit consumables from the BOM and consumable costs use ASM industry averages.
default: true
includeBomByproducts boolean Include valuable byproduct/co-product components in the BOM. When false, the AI is instructed to omit byproducts and no byproduct credit is taken off the total.
default: true
importId string<uuid> Parent Import when this run is one row of a file import
uuid
rowIndex number Zero-based row index in the source import file
min: 0
rowData object Raw parsed source row (import provenance)

Responses

201
201 Response
FieldTypeDescription
data JobDto
id string Unique identifier for the job
parentJobId string FK to a parent job when this job is a sub-task of a pipeline job
importId string FK to the parent Import when this job is one row of a file import
rowIndex number Zero-based row index within the source import file (set for import rows)
type* string Job type identifier (e.g., bom_generation, cost_analysis)
status* string Current execution status of the job
enum: pending, in_progress, completed, failed, skipped
dispatchClaimedAt string Dispatch lease (DLN-329). Cloud Tasks delivers at-least-once, so the dispatch worker claims a run by compare-and-set on this column before calling the Cloud Run Jobs API; a delivery that loses the race skips. Set while `status` is still 'pending' — the orchestrator, not the worker, owns the move to 'in_progress'. The lease expires (see DISPATCH_CLAIM_LEASE_MS) so a worker that dies mid-claim doesn't strand the run: the next redelivery re-claims it.
dispatchRequestedAt string When a launch was last requested for this run (DLN-613): stamped at creation and re-stamped by every retry, alongside the reset of `started`/`dispatchClaimedAt`. Exists so a run that is dispatched but never claimed — enqueueDispatch failing outright, or every Cloud Tasks redelivery of it being exhausted — still has something to age against. `started` and `dispatchClaimedAt` take precedence when present; this is the last-resort fallback the isRunStalled predicate uses when both of those are null. Deliberately reset on retry (unlike createdAt) so a retried run ages from the retry, not its original submission — see stale-run.constants.ts for why that distinction matters.
autoRetryCount number How many times this run has been retried automatically since it was last retried by a person. Automatic retry stops once this reaches its limit; retrying the run by hand resets it to 0. Absent on runs created before automatic retry existed, which means 0.
payload object Input data for the job (structure depends on job type)
result object Output data from the job (structure depends on job type)
step string Current step label for progress tracking (e.g., fetching_data, analyzing)
started string Timestamp when the job started executing
completed string Timestamp when the job completed successfully
errored string Timestamp when the job encountered an error, if applicable
errorMessage string Error message describing what went wrong, if applicable
source string How this cost-analysis job was triggered. Derived server-side at creation from the request shape (DLN-296): manual = direct Add Product; bulk_import = one row of a file import (see importId); iteration = feedback-approved rerun of an existing analysis; iteration_new_product = feedback approval that forks a new product/analysis.
enum: manual, bulk_import, iteration, iteration_new_product
submittedById string App-user id (x-app-user-id) of the user who triggered the job — the submitter for a manual run, the approving admin for an iteration/new-product run, or the person who asked for a bom_refresh. A plain id string (not necessarily a UUID); resolve to a display name via user-service.
meta object
400 Invalid request, or (with TRANSACTIONS) a reportingCurrency other than the currency of the product's purchases
403 Not available to external users — running a cost analysis is internal-only (DLN-321); or a user call naming an IMPACT-route transaction import, whose runs only the import processor submits
404 Referenced importId or signalItemId not found in the tenant
409 Duplicate job id; referenced import is not PROCESSING (reason IMPORT_NOT_PROCESSING); or, for a product of an IMPACT-route transaction import, the product already has an IMPACT run (reason PRODUCT_ALREADY_ANALYSED)
503 Product stub creation disabled in this environment
GET /pipeline/cost-analysis/{id}

Get a cost analysis or bom-refresh job by ID

Parameters

NameInTypeRequiredDescription
id path string<uuid> required

Responses

200
200 Response
FieldTypeDescription
data CostAnalysisJobDto
id string Unique identifier for the job
parentJobId string FK to a parent job when this job is a sub-task of a pipeline job
importId string FK to the parent Import when this job is one row of a file import
rowIndex number Zero-based row index within the source import file (set for import rows)
type* string Job type identifier (e.g., bom_generation, cost_analysis)
status* string Current execution status of the job
enum: pending, in_progress, completed, failed, skipped
dispatchClaimedAt string Dispatch lease (DLN-329). Cloud Tasks delivers at-least-once, so the dispatch worker claims a run by compare-and-set on this column before calling the Cloud Run Jobs API; a delivery that loses the race skips. Set while `status` is still 'pending' — the orchestrator, not the worker, owns the move to 'in_progress'. The lease expires (see DISPATCH_CLAIM_LEASE_MS) so a worker that dies mid-claim doesn't strand the run: the next redelivery re-claims it.
dispatchRequestedAt string When a launch was last requested for this run (DLN-613): stamped at creation and re-stamped by every retry, alongside the reset of `started`/`dispatchClaimedAt`. Exists so a run that is dispatched but never claimed — enqueueDispatch failing outright, or every Cloud Tasks redelivery of it being exhausted — still has something to age against. `started` and `dispatchClaimedAt` take precedence when present; this is the last-resort fallback the isRunStalled predicate uses when both of those are null. Deliberately reset on retry (unlike createdAt) so a retried run ages from the retry, not its original submission — see stale-run.constants.ts for why that distinction matters.
autoRetryCount number How many times this run has been retried automatically since it was last retried by a person. Automatic retry stops once this reaches its limit; retrying the run by hand resets it to 0. Absent on runs created before automatic retry existed, which means 0.
payload object Input data for the job (structure depends on job type)
step string Current step label for progress tracking (e.g., fetching_data, analyzing)
started string Timestamp when the job started executing
completed string Timestamp when the job completed successfully
errored string Timestamp when the job encountered an error, if applicable
errorMessage string Error message describing what went wrong, if applicable
source string How this cost-analysis job was triggered. Derived server-side at creation from the request shape (DLN-296): manual = direct Add Product; bulk_import = one row of a file import (see importId); iteration = feedback-approved rerun of an existing analysis; iteration_new_product = feedback approval that forks a new product/analysis.
enum: manual, bulk_import, iteration, iteration_new_product
submittedById string App-user id (x-app-user-id) of the user who triggered the job — the submitter for a manual run, the approving admin for an iteration/new-product run, or the person who asked for a bom_refresh. A plain id string (not necessarily a UUID); resolve to a display name via user-service.
result any Result shape depends on the job type: cost_analysis jobs return this cost-analysis result; a bom_refresh job's result instead carries the refresh outcome — per-outcome refresh counts (update / ignore / flag-for-review / preserved / held / total, plus anchored: the parts priced from the customs anchor, already counted under update), materiality (material, nodeCrossings, rollupCrossed), catalog/lookup counts (catalogEntries / lookups / lookupsSaved), and analysisId when a material price move minted a new version (absent when prices were checked with no material change).
stalled* boolean Derived, not stored: true when the run still reads pending or in_progress long after a live execution would have reported. A run whose container dies keeps that status forever — nothing writes a correction, and the Cloud Run task retry that used to mask it was removed — so `status` alone reports a dead run as running. Aged against the job timeout plus a clock-skew margin, from `started`; before the orchestrator sets that, from `dispatchClaimedAt`; and before a dispatch is claimed at all, from `dispatchRequestedAt`. That last case also catches a run whose dispatch is merely still queued behind the dispatch rate limit, so on a large import true can mean "not started yet" rather than "died". Nothing clears the flag on its own: a stalled run stays stalled until a retry re-stamps its anchor. POST /pipeline/cost-analysis/:id/retry is that path, and it can still refuse — 409 when the run already produced an analysis or the row changed underneath the retry, 400 for a bom_refresh whose payload carries no productId.
meta object
404 Not found

Cost Analysis

Retrieve completed cost-analysis results and component-level details.

GET /cost-analysis/{id}/feedback

List feedback submitted on a cost analysis (DLN-311)

Returns the feedback submissions for the analysis, newest-first, tenant-scoped. An analysis with no feedback yet returns an empty list. Responds 404 only when the cost analysis is unknown or belongs to another tenant.

Parameters

NameInTypeRequiredDescription
id path string<uuid> required
limit query number optional
offset query number optional
count query boolean optional Include totalCount of unbounded results in response meta

Responses

200
200 Response
FieldTypeDescription
data AnalysisFeedbackDto[]
id* string Unique identifier for the analysis-feedback record
costAnalysis* any The cost analysis this feedback is for (the ticket's analysisId). Validated relation — accepts the related object or its id string (@ValidateRelatedOrId). - accepts full object, id string, or id number
submittedById* string User who submitted the feedback
submittedByName string Display name of the submitter, captured at submission time
submittedAt* string When the feedback was submitted
origin string Whether the iteration was submitted by a Dalinea-internal or an external (customer) user. Derived server-side at submit from the gateway-set x-user-type header (never the request body). Internal-origin iterations are excluded from the rerun count/cap and hidden from customer-facing history (DLN-334). Absent on rows predating DLN-334 → treated as external.
enum: internal, external
changeType* string Kind of change the feedback requests
enum: new_analysis, iteration_no_rerun, iteration_with_rerun
feedbackStatus* string Review lifecycle status
enum: auto_approved, submitted, under_review, approved, rejected
corrections any The user-submitted correction payload. Bundled rather than flattened: the individual values are reported on, not filtered. The concrete grammar of the free-form sub-objects is owned by the submit flow (DLN-194).
normalizedCorrections any The admin-normalized counterpart of `corrections`, produced during review (DLN-195). Same field set, plus `changeType` when the admin reclassifies the request.
reviewedById string Admin who reviewed the feedback
reviewedByName string reviewedByName
reviewedAt string reviewedAt
releasedReason string Set when the system, not a reviewer, rejected a with-rerun iteration because its run will never produce a version: run_failed when the run failed or died, dispatch_failed when the pipeline refused to start it. A rejection carrying it ends the iteration round, so the version it ran from is no longer "in progress"; a reviewer's rejection never does.
enum: run_failed, dispatch_failed
bomValidationOverrides string[] BOM validation rules the admin chose to override for this iteration. Each item identifies a rule and the override decision; shape owned by the admin review flow (DLN-195).
resultAnalysisId string Nullable pointer to the resulting cost analysis (the new v1 for new_analysis, or the new version for iterations). Stored as a plain uuid (deferred-FK) — a second validated relation to cost-analysis is deferred to avoid multi-relation disambiguation; AC requires only the submitted analysis FK to be validated.
evaluationMessage string System/admin message describing the evaluation outcome
meta object
totalCount integer Unbounded result count. Only present when ?count=true is passed.
404 Cost analysis not found
POST /cost-analysis/{id}/feedback

Submit category-manager feedback on a cost analysis (DLN-194)

For external submitters, the first submission claims the feedback-owner lock (403 for a different user). DLN-334: internal (Dalinea) submitters are exempt — they neither claim the lock nor are blocked by an existing owner (no 403), and their iterations do not count toward or hit the 3-round rerun cap. iteration_with_rerun and new_analysis enter the admin review queue (202, status → pending_review); iteration_with_rerun is rejected with 422 once the chain has consumed 3 rerun rounds (external only). iteration_no_rerun recomputes synchronously (no AI) and resolves immediately (200) with the auto-approved feedback pointing at the newly persisted version via resultAnalysisId. In an organization holding BOTH the EXTERNAL_COST_ANALYSIS_RUNS and EXTERNAL_ITERATION_AUTO_EXECUTE overrides, an EXTERNAL iteration_with_rerun skips the queue entirely — it is stored auto_approved and the rerun is dispatched on submit (202). Holding EXTERNAL_COST_ANALYSIS_RUNS alone permits an external admin to approve a run by hand but does NOT skip the queue. The round cap and the first-submitter lock still apply, and new_analysis, internal submitters and unflagged organizations are unaffected.

Parameters

NameInTypeRequiredDescription
id path string<uuid> required
x-app-user-id header string required
x-user-type header string optional Gateway-set INTERNAL/EXTERNAL user type (DLN-334). Server-injected — clients never supply it; the gateway strips inbound copies. Absent → treated as external.
x-feature-flags header string optional Gateway-resolved feature flags for this (organization, user). Server-injected — clients never supply it; the gateway strips inbound copies. Absent → no flags, i.e. the submission is queued for review.
x-user-roles header string optional Gateway-resolved role names for this (organization, user). Server-injected — clients never supply it; the gateway strips inbound copies. Read only by the manufacturer-financials override, which needs DALINEA_ADMIN. Absent → no roles.

Request Body

FeedbackSubmissionDto
FieldTypeDescription
changeType* string Kind of change requested. iteration_no_rerun resolves immediately; iteration_with_rerun and new_analysis enter the admin review queue.
enum: new_analysis, iteration_no_rerun, iteration_with_rerun
submittedByName string Submitter display name, captured for the review queue. Non-authoritative — the identity used for the ownership lock comes from x-app-user-id.
corrections any The user-submitted correction payload (per the analysis-feedback grammar).

Responses

202
202 Response
FieldTypeDescription
data AnalysisFeedbackDto
id* string Unique identifier for the analysis-feedback record
costAnalysis* any The cost analysis this feedback is for (the ticket's analysisId). Validated relation — accepts the related object or its id string (@ValidateRelatedOrId). - accepts full object, id string, or id number
submittedById* string User who submitted the feedback
submittedByName string Display name of the submitter, captured at submission time
submittedAt* string When the feedback was submitted
origin string Whether the iteration was submitted by a Dalinea-internal or an external (customer) user. Derived server-side at submit from the gateway-set x-user-type header (never the request body). Internal-origin iterations are excluded from the rerun count/cap and hidden from customer-facing history (DLN-334). Absent on rows predating DLN-334 → treated as external.
enum: internal, external
changeType* string Kind of change the feedback requests
enum: new_analysis, iteration_no_rerun, iteration_with_rerun
feedbackStatus* string Review lifecycle status
enum: auto_approved, submitted, under_review, approved, rejected
corrections any The user-submitted correction payload. Bundled rather than flattened: the individual values are reported on, not filtered. The concrete grammar of the free-form sub-objects is owned by the submit flow (DLN-194).
normalizedCorrections any The admin-normalized counterpart of `corrections`, produced during review (DLN-195). Same field set, plus `changeType` when the admin reclassifies the request.
reviewedById string Admin who reviewed the feedback
reviewedByName string reviewedByName
reviewedAt string reviewedAt
releasedReason string Set when the system, not a reviewer, rejected a with-rerun iteration because its run will never produce a version: run_failed when the run failed or died, dispatch_failed when the pipeline refused to start it. A rejection carrying it ends the iteration round, so the version it ran from is no longer "in progress"; a reviewer's rejection never does.
enum: run_failed, dispatch_failed
bomValidationOverrides string[] BOM validation rules the admin chose to override for this iteration. Each item identifies a rule and the override decision; shape owned by the admin review flow (DLN-195).
resultAnalysisId string Nullable pointer to the resulting cost analysis (the new v1 for new_analysis, or the new version for iterations). Stored as a plain uuid (deferred-FK) — a second validated relation to cost-analysis is deferred to avoid multi-relation disambiguation; AC requires only the submitted analysis FK to be validated.
evaluationMessage string System/admin message describing the evaluation outcome
meta object
403 Cost analysis is owned by another feedback submitter (external submitters only; internal submitters are exempt)
404 Cost analysis not found
409 Auto-executing organizations only: the analysis is not in a state a re-run can start from — a run is already in flight, its first run has not finished, a submission is already queued for review, or it failed. Returned instead of starting a second concurrent run on the same chain. A spent rerun round is a separate answer (422), never this one.
422 Rerun round limit reached, or the submission carries no corrections to apply. An empty correction set is refused rather than recomputed: it would mint a version identical to its parent and report success.
POST /cost-analysis/{id}/review

Mark a cost-analysis version reviewed

Records that a person at the customer has looked over this version and found it right. Appends a review to the version's history and sets its latest-review fields (reviewedById, reviewedByName, reviewedAt, reviewedCostAnalysisId, reviewedVersionLabel) in one transaction. Nothing else changes: no new version, no run, no rerun round, and the feedback owner is neither checked nor claimed. Reviewing the same version again adds another review and the latest one shows. Open to every customer role (Viewer and up); Dalinea staff are refused.

Parameters

NameInTypeRequiredDescription
id path string<uuid> required
x-app-user-id header string optional Gateway-set identity of the reviewer. Server-injected; a request without it is refused with 400.
x-user-type header string optional Gateway-set INTERNAL/EXTERNAL user type. Server-injected; any caller that is not EXTERNAL is refused with 403.

Request Body

CostAnalysisReviewRequestDto
FieldTypeDescription
reviewerName string Display name of the reviewer, shown as "Reviewed by NAME". Attribution only — the reviewer is identified by x-app-user-id.
maxLen: 255
versionLabel string The version label the reviewer saw, such as "v3". Display-only, like reviewerName: stored as the review's versionLabel and the version's reviewedVersionLabel. Absent, the version's stored versionLabel is used. The version itself is identified by the path id.
maxLen: 32

Responses

200
200 Response
FieldTypeDescription
data CostAnalysisReviewDto
id* string Unique identifier for the review row
costAnalysisId* string The @cost-analysis version that was reviewed.
productId* string Copied from the analysis so the history reads per product on its own.
versionLabel string versionLabel of the reviewed analysis, copied at review time.
reviewerUserId* string The reviewing user, from the request's x-app-user-id header.
reviewerName string Display name of the reviewer, as supplied on the review request. Attribution only; reviewerUserId is the authoritative link. Nullable.
reviewedAt* string When the review was recorded. Server-stamped.
meta object
400 No reviewer identity on the request
403 Caller is not a customer user (Dalinea staff, or no user type), or holds no role in this organization
404 Cost analysis not found in this tenant
409 The version cannot be reviewed now: its run has not finished or failed, an iteration on it is queued for review or running, or an approved rerun from it has not produced its result. A version is reviewable when it is analysis_done, or pending_feedback once its rerun has finished.
GET /cost-analysis/{id}/reviews

List the reviews of a cost-analysis version

Every review recorded directly on this version, newest first. The version's latest-review fields can instead name a review of an earlier version that a refresh carried forward.

Parameters

NameInTypeRequiredDescription
id path string<uuid> required

Responses

200
200 Response
FieldTypeDescription
data CostAnalysisReviewDto[]
id* string Unique identifier for the review row
costAnalysisId* string The @cost-analysis version that was reviewed.
productId* string Copied from the analysis so the history reads per product on its own.
versionLabel string versionLabel of the reviewed analysis, copied at review time.
reviewerUserId* string The reviewing user, from the request's x-app-user-id header.
reviewerName string Display name of the reviewer, as supplied on the review request. Attribution only; reviewerUserId is the authoritative link. Nullable.
reviewedAt* string When the review was recorded. Server-stamped.
meta object
totalCount integer Unbounded result count. Only present when ?count=true is passed.
404 Cost analysis not found in this tenant
GET /cost-analysis/portfolio/summary

Portfolio Overview savings-potential summary

Aggregates each product's primary cost analysis into Likely/Possible/Unlikely/Unknown savings buckets (counts, percentages, summed savings potential in minor currency units) plus the top-N savings opportunities. A product with no resolvable primary contributes its latest completed analysis instead. Soft-deleted analyses never contribute. Every total is in USD, the currency each analysis is computed in; mixedCurrencies flags a book holding any product that reports in another currency, and each row carries its reportingCurrency and presented figures. Tenant-wide, cached briefly in memory.

Parameters

NameInTypeRequiredDescription
topLimit query integer optional Rows in topProducts (default 5, max 50)
recentLimit query integer optional Rows in recentAnalyses (default 5, max 50)

Responses

200
200 Response
FieldTypeDescription
data PortfolioSummaryDto
currency* string Denomination of every total in this summary: always USD. The buckets sum the USD base figures of each contributing analysis whatever currency that analysis reports in.
mixedCurrencies* boolean True when any contributing analysis reports in a currency other than USD. The totals are still USD; the per-row reportingCurrency and presented figures say what each product shows.
totalProducts* number Products with >=1 completed cost analysis — the percentage denominator
buckets* PortfolioBucketsDto
likely* PortfolioBucketStatDto
possible* PortfolioBucketStatDto
unlikely* PortfolioBucketStatDto
unknown* any Products whose contributing analysis has no savings opportunity or no usable should-cost
topProducts* PortfolioTopProductDto[]
productId* string<uuid> Product the opportunity is for
uuid
costAnalysisId* string<uuid> Contributing cost analysis id — the product's primary, or its latest completed analysis when no primary resolves
uuid
savingsOpportunityId* string<uuid> Savings opportunity id
uuid
shouldCostAmount* number Should-cost in minor currency units
savingsAmount* number Savings amount (reference price - should-cost) in minor units
savingsPct* number Savings as a fraction of should-cost (0..1)
bucket* string Bucket this product falls into
enum: likely, possible, unlikely, unknown
currency* string Denomination of the base figures on this row: always USD, the currency every analysis is computed in.
reportingCurrency* string The currency the contributing analysis reports in (USD unless the run asked for another). When it differs from currency, `presented` carries the figures in it.
presented* any The base figures in reportingCurrency, read from the stored twins. Null when the analysis reports in USD.
recentAnalyses* PortfolioRecentAnalysisDto[]
productId* string<uuid> Product the analysis is for
uuid
costAnalysisId* string<uuid> Cost analysis id
uuid
analysisDate* string When the analysis was performed (ISO 8601)
shouldCostAmount* number Should-cost in minor currency units
savingsAmount* number Savings amount in minor units; null when the analysis has no opportunity
savingsPct* number Savings as a fraction of should-cost (0..1); null when no opportunity or no usable should-cost
currency* string Denomination of the base figures on this row: always USD, the currency every analysis is computed in.
reportingCurrency* string The currency the contributing analysis reports in (USD unless the run asked for another). When it differs from currency, `presented` carries the figures in it.
presented* any The base figures in reportingCurrency, read from the stored twins. Null when the analysis reports in USD.
generatedAt* string When the summary was computed (ISO 8601)
meta object
400 topLimit or recentLimit is not a valid integer
503 The primary-analysis pointer could not be resolved and no previously computed summary is held.
GET /cost-analysis/by-product/{productId}

Get a product's primary cost analysis

Returns the product's PRIMARY cost analysis — the analysis of record. A product with no primary, or whose primary does not resolve to a live completed analysis of its own within the requested filterScope, falls back to its latest completed analysis, ordered exactly as the portfolio aggregates order theirs (newest analysisDate, then highest id — analysisDate alone is caller-supplied and can tie). Called without filterScope, this normally names the same analysis the portfolio aggregates counted for that product. Two things make the two surfaces differ. First, the aggregates are cached per tenant for up to 90 seconds — up to an hour while the primary-analysis pointer is unresolvable and a previously computed answer is being served — and nothing invalidates that cache when a primary moves, so for that window a dashboard figure can still name the previous analysis while this lookup already names the new one. Second, the aggregates apply no filterScope: request one here and a product whose primary sits outside it is answered from its latest in-scope analysis instead, or 404s if it has none, while the aggregates go on counting the pinned one. The answer is a single merged object (run-level record + rollup buckets as components). Monetary values are in minor currency units. Responds 202 with job status when no completed analysis exists but a pipeline run is pending or in progress (the in-progress check is product-level and not constrained by filterScope). Responds 404 when neither exists, including when the pipeline service is unreachable. Responds 503 when a completed analysis exists but the primary could not be resolved: the endpoint reports that rather than answering from a different basis.

Parameters

NameInTypeRequiredDescription
productId path string<uuid> required
filterScope query string optional Comma-separated dimension:value filters constraining which completed analyses qualify, as the primary and as the fallback alike. The portfolio aggregates apply no such filter, so requesting one is the one case where this endpoint deliberately names a different analysis than the dashboard figure for the same product. Supported dimensions: countryOfOrigin, importCountry. Example: countryOfOrigin:CN,importCountry:US

Responses

200
200 Response
FieldTypeDescription
data CostAnalysisResultDto1
id* string<uuid> Cost analysis id
uuid
status* string Status of the cost analysis
enum: analysis_done
analysisDate* string When the analysis was performed (ISO 8601)
productId* string<uuid> Product the analysis belongs to
uuid
currency string Currency code for all monetary values (e.g., USD)
bomId string<uuid> Bill of materials the analysis was computed from
uuid
countryOfOrigin string ISO country code of origin
importCountry string ISO country code of import destination
shouldCostAmount number Theoretical production cost in minor currency units
shouldCostStdErr number Standard error of the should-cost estimate in minor currency units
qaVerdict object QA Evaluation verdict (DLN-158). Null when QA was not enabled for this analysis.
adjustments AdjustmentsDto[] Adjustments applied beyond the industry-average baseline
type* string Identifier for the kind of adjustment applied
enum: manufacturer_financials, yield_adjustment, compliance_overhead, setup_amortization, named_adjustment, tariff, indirect_tariff, volume_discount, supplier_purchasing_power
source* string Origin of this adjustment
enum: user_provided, ai_estimate, analyst_judgment, system, edgar_auto, operator_entry
label string Human-readable description of what was adjusted
metadata object Adjustment-specific qualitative context (no dollar amounts). For type="manufacturer_financials": may include - cogsRatioAdjusted (boolean) - profitMarginOverridden (boolean) - fiscalYear (integer): the FY the financials describe - companyFinancialsId (uuid): the specific company_financials row that fed this run, enabling audit even after the cache rolls to a newer filing - financialsSource ("edgar" | "manual" | "llm" | "dto"): where the financials came from for this run - sgaCompleteness ({found:[...], missing:[...]}): presence of each financial figure the SG&A selection reads — revenue, cogs, the sm/ga pair, R&D, and the combined SG&A line — plus the reported operating income, tracked here as drift-detection evidence even though DLN-913 margin derivation never reads it: the operating margin comes only from revenue, cogs and SG&A. `missing` is not a defect signal: a filer that legitimately never files a given tag (e.g. a decomposed filer with no combined line) reports it missing without anything being wrong. - operatingMarginSource ("edgar_sga" | "asm_industry_average"): which route produced the margin PAIR. "edgar_sga" — the manufacturer's own margins, the operating half reconstructed from an SG&A sum: `(revenue - cogs - sga) / revenue` (the name is a misnomer for the manual-DTO case too; kept for backward compatibility). This is the ONLY way a manufacturer's own operating margin can be adopted. DLN-913 reversed an earlier decision (PR #636) to prefer a filer's own reported `us-gaap:OperatingIncomeLoss`: we cannot know what a filer included when it calculated that figure, so we derive the margin ourselves and never adopt theirs, however it compares to our own gross margin. "edgar_operating_income" was that now-retired source value; it can no longer appear. "asm_industry_average" — BOTH margins came from the industry baseline. Since DLN-913 the manufacturer's gross and operating margin are adopted together or not at all, so this value means the whole pair is industry-sourced; before DLN-913 a run could carry the manufacturer's gross margin beside an industry or derived operating margin. - industryAverageReason ("no_margin_pair" | "implausible_gross_margin"): why the run fell back. Present only when operatingMarginSource is "asm_industry_average". "no_margin_pair" — financials reached the engine (at least one of COGS, SG&A or the GROSS margin was supplied — an operating margin alone does not emit this adjustment) but did not yield both margins, so neither was adopted; this covers a half pair whose present half is the gross margin, and a filing that resolved some figure but neither margin (e.g. SG&A with no revenue). "implausible_gross_margin" — the DLN-888 band withheld the whole set. A run with NO manufacturer financials at all (nothing supplied) emits no manufacturer_financials adjustment and so carries neither this key nor operatingMarginSource — the frontend's own `financialsStatus`/`financialsSource` fields on computeInputs are what explain that case, not this metadata; so does a run whose only supplied figure is an operating margin — absence of the adjustment does not by itself mean nothing was supplied. - llmCitations ([{field, url, note}]): present only when financialsSource == "llm" For type="tariff" (direct product-level import tariff): may include - importCountry (string) - countryOfOrigin (string) - ratePercent (number) For type="indirect_tariff" (tariffs on foreign-sourced BOM components): may include - components (object): per-component breakdown of the indirect tariff impact
components* CostComponentDto[] Cost breakdown by rollup bucket
id* string<uuid> Unique identifier of the bucket row
uuid
name* string Name of the cost component (e.g., "Direct Materials")
currency* string Currency code (e.g., USD)
cost* number Cost amount in minor currency units (e.g., cents)
costMin* number Minimum estimated cost in minor currency units
costMax* number Maximum estimated cost in minor currency units
meta object
202
202 Response
FieldTypeDescription
data CostAnalysisInProgressDto
jobId* string<uuid> Pipeline job id to poll via GET /pipeline/cost-analysis/:id
uuid
status* string Job status
enum: pending, in_progress
step string Current pipeline step label
meta object
400 Malformed filterScope or unsupported dimension
404 No completed cost analysis found for the product
503 The primary-analysis pointer could not be resolved.
GET /cost-analysis/{id}/versions

Get the full version history of a cost analysis

Returns every version in the analysis chain in chronological order (oldest first). Accepts ANY version id in the chain — it walks both backward (prior versions) and forward (later iterations) from the given id. Each entry is a summary; full data stays on GET /cost-analysis/:id. Responds 404 when the id is unknown or belongs to another tenant.

Parameters

NameInTypeRequiredDescription
id path string<uuid> required

Responses

200
200 Response
FieldTypeDescription
data CostAnalysisVersionDto[]
id* string<uuid> Cost analysis id for this version
uuid
versionLabel* string Human-readable label for this version
changeType* string Iterations changeType of the feedback that produced this version; null for the root (no driving feedback)
status* string Lifecycle status of this version
feedbackOwnerId* string<uuid> User who owns the feedback loop for this version
uuid
feedbackOwnerName* string Display name of the feedback owner
changeSummary* string Short summary of what changed in this version
feedbackSummary* string Short summary of the feedback that drove this version
createdAt* string When this version was created (ISO 8601)
hasBomValidationOverrides* boolean True when the driving feedback carries confirmed BOM validation overrides
rerunInFlight* boolean True while an approved rerun from this version has not produced its result. A version whose rerun finished keeps the status pending_feedback, so read this, not the status, to know whether it is busy.
meta object
totalCount integer Unbounded result count. Only present when ?count=true is passed.
404 Cost analysis not found
GET /cost-analysis/{id}

Get cost-analysis by ID

Parameters

NameInTypeRequiredDescription
id path string<uuid> required
related query string[] optional Include related entities. Omit for all, "false" for none, or specify field names (repeatable, supports dot notation)

Responses

200
200 Response
FieldTypeDescription
data CostAnalysisDto
id* string Unique identifier for the cost analysis record
status* string Status of the cost analysis lifecycle. analysis_done = an AI pipeline run finished (the former "completed"); pending_review / pending_feedback / iteration are the human-in-the-loop iterations states (DLN-190).
enum: pending, analysis_done, pending_review, pending_feedback, iteration, failed
analysisDate* string Date and time when the cost analysis was performed
productId string FK to the @product for which the cost analysis is performed
currency string Denomination of the base money columns on this analysis and its children (shouldCostAmount, bucket and component costs, reference prices, savings). The engine computes in USD, so this is USD. The currency the customer sees is reportingCurrency; its figures live in the `presented` twins.
reportingCurrency string The currency the analysis is reported in. When it differs from `currency`, every money-bearing row carries a `presented` twin in this currency, converted from the USD base at fxRate. Rows produced before this field existed are USD.
fxRate number Units of reportingCurrency per one US dollar, as stored on the annual exchange-rate row cited by fxRateId at the time of the run. Fixed on the analysis so re-opening it later shows the same figures whatever the rate table has done since. Null when reportingCurrency is USD.
fxRateSource string Publisher of the rate, copied from the exchange-rate row (e.g. Bank of Canada). Null when USD.
fxRateAsOf string The date the rate reflects, copied from the exchange-rate row. Null when USD.
fxRateId string The exchange-rate row the conversion cites, for audit. Null when USD.
presented any The headline figures in reportingCurrency: each is round_half_up(base cents x fxRate). Null when reportingCurrency is USD.
bomId string FK to the bill of materials used as the basis for this cost analysis
countryOfOrigin string The country of origin for the product. This may impact the cost analysis due to factors like tariffs, shipping costs, and local market conditions.
importCountry string The country into which the product is imported. Used for tariff rate lookups. Defaults to US if not provided.
shouldCostAmount number Theoretical cost to produce the product, in minor currency units (e.g., cents)
shouldCostStdErr number Approximation of the standard error of the should-cost estimate, in minor currency units (e.g., cents)
scenarioContext string Named rate scenario this analysis ran under. Nullable.
analysisBasis string Basis of the analysis (e.g. 'should_cost').
priorCostAnalysisId string Self-FK to the previous CA in a temporal lineage. Nullable (DLN-152 writes).
changeType string Classification of why this CA exists: BOM_REVISION for a new/updated bill of materials, RATE_REFRESH for a rate-only re-run. DLN-152/153 write this field.
enum: BOM_REVISION, RATE_REFRESH
versionLabel string Human-readable label for this CA version.
rerunRoundCount number Denormalized count of with-rerun iteration rounds consumed on this chain. Defaults to 0; the feedback flow (DLN-194) increments it and enforces the round limit, rather than walking the chain on every check.
isInternalIteration boolean True when this CA version was produced by a Dalinea-internal iteration (DLN-334). Denormalized from the producing analysis-feedback's origin so the version-chain query can hide internal versions from external callers without re-deriving origin per row. Internal versions are excluded from the rerun count/cap. Defaults to false (rows predating DLN-334 and non-iteration analyses are external/customer-facing). Set only by the submit service / the orchestrator; immutable thereafter so a later PUT can't unhide/hide a version.
feedbackOwnerId string User who owns the feedback loop for this analysis (first-submitter lock). Nullable until claimed. Written only by the claim, the release, and the carry-forward that copies it onto each new version — all raw SQL, which does not pass through the immutable check. Immutable to the generated CRUD routes so an organization ADMIN cannot clear, steal or squat the claim through a PUT and bypass the release route's Dalinea-admin gate.
feedbackOwnerName string Display name of the feedback owner, captured at claim time. Nullable. Immutable to generated CRUD for the same reason as feedbackOwnerId: a name writable apart from the id it labels is the half-carry the inheritance rule already refuses.
reviewedById string User id of the person at the customer who last reviewed this version, or the version a refresh carried the review from. Nullable until reviewed.
reviewedByName string Display name of that reviewer, captured at review time. Nullable.
reviewedAt string When that review was recorded. Nullable.
reviewedCostAnalysisId string The version the reviewer actually reviewed. Equals id for a direct review; names an earlier version when a refresh carried the review forward. Nullable.
reviewedVersionLabel string Label of the reviewed version as the reviewer saw it (for example v3), kept for display. Nullable.
changeSummary string Short human-readable summary of what changed in this version. Nullable.
feedbackSummary string Short human-readable summary of the feedback that drove this version. Nullable.
qaVerdict object QA evaluation outcome. Nullable (DLN-158 writes; DLN-138 leaves null).
computeInputs any Snapshot of the run-time engine inputs NOT otherwise reconstructable from this analysis — raw financials scalars, the derived margins actually passed, and the option flags — plus the resolved tariff scalars for robust replay. Written by the orchestrator at the cost-analysis compute step (DLN-269). Makes the analysis self-describing so a recompute (iteration no-rerun, DLN-194) or audit can faithfully replay the run. Nullable on rows produced before this field existed. See ADR 0071. The financials* keys below are written on every run the orchestrator computes, carrying null when they have nothing to say — a missing VALUE is not a missing KEY, so branch on the value and never on `in`. They are absent outright only on a version carried forward from before the field existed, because a no-rerun recompute replays the prior snapshot rather than rebuilding it.
userTariffOverride any A user-pinned HTS code and/or direct tariff rate that must survive AI pipeline reruns (DLN-202). Set from an iteration_no_rerun feedback submission and copied forward to every new version in the chain. The tariff-resolution step (orchestrator Step 3d) and the recompute engine read it as a CONSTRAINT before resolving: a pinned tariffRatePct is authoritative (used instead of dataset/AI resolution), while a pinned htsCode only overrides which code is looked up (the rate is still resolved). The resulting direct tariff adjustment is emitted with source=user_provided so GET /cost-analysis/:id distinguishes it from AI-estimated values. Nullable when the user has not pinned anything.
buckets CostAnalysisBucketDto[] Rollup buckets (Direct Materials, Salaries, Import Tariff, …) derived from component results. - accepts array of full objects, id strings, or id numbers
id* string Unique identifier for the cost analysis bucket record
name* string Name of the cost component (e.g., "direct materials", "production wages", etc.)
category* string Coarse classification (e.g. 'materials', 'labor', 'overhead', 'tariff', 'profit').
costAnalysis* any The parent cost analysis - accepts full object, id string, or id number
currency* string Currency code for the cost analysis (e.g., USD, EUR)
cost* number Cost amount for this component in minor currency units (e.g., cents)
costMin* number Minimum estimated cost for this component in minor currency units (e.g., cents)
costMax* number Maximum estimated cost for this component in minor currency units (e.g., cents)
presented any The same figures in the analysis's reportingCurrency, each round_half_up(base x fxRate). Null when the analysis reports in USD.
supportingData object Additional supporting data for this cost component as key-value pairs (e.g., dataset references, assumptions, calculation notes)
componentResults CostAnalysisComponentResultDto[] Per-BOM-component cost results (baselineCost, finalCost, stdErr). - accepts array of full objects, id strings, or id numbers
id* string Unique identifier for the component result
costAnalysis* any The parent cost analysis - accepts full object, id string, or id number
bomComponentId* string FK to BOM_COMPONENT.id. Stored as a plain uuid (no $ref) because the BOM lives in a separate standalone service — same convention as COST_ANALYSIS.productId and COST_ANALYSIS.bomId.
currency* string Currency code (e.g. USD, EUR)
baselineCost* number Pre-adjustment cost for this component, in minor currency units (cents).
finalCost* number Post-adjustment cost for this component, in minor currency units (cents).
stdErr number Approximate standard error of finalCost, in cents. Nullable (populated when Monte Carlo ran).
costMin number Lower bound (≈P10) of finalCost in minor currency units (cents). Nullable — populated only when Monte Carlo ran (same condition as stdErr).
costMax number Upper bound (≈P90) of finalCost in minor currency units (cents). Nullable — populated only when Monte Carlo ran (same condition as stdErr).
presented any The same figures in the analysis's reportingCurrency, each round_half_up(base x fxRate). Null when the analysis reports in USD.
rateSnapshots CostAnalysisRateSnapshotDto[] Frozen rate snapshots (tariff, labor, energy, localization) used in this analysis - accepts array of full objects, id strings, or id numbers
id* string Unique identifier for the rate snapshot
costAnalysis any The parent cost analysis - accepts full object, id string, or id number
rateType* string Kind of rate captured
enum: tariff, labor, energy, localization, manufacturer_financials
layer string base MFN rate, or a surcharge layered on top (e.g. Section 301)
enum: base, surcharge
source* string Authoritative source of the rate. USER = a user-pinned tariff (DLN-202): the pin is a recorded rate fact, emitted as a base-layer snapshot so the analysis stays reproducible (ADR 0047) and the pin is queryable.
enum: USITC HTS, WITS, AI, INTERNAL, EDGAR, USER
sourceTable string Dataset table the rate was read from (e.g. usitc_hts_10digit, wits_tariff_rates)
sourceVersionId string Source dataset version stamp (e.g. "2026HTSRev8" for USITC, "2023" for WITS)
lookupKey* string The HTS/HS6 code looked up
value* number Rate as a decimal fraction (e.g. 0.075 for 7.5%)
unit* string Unit of the value. "percent" = ad-valorem percent expressed as a decimal fraction (e.g. 0.075 for 7.5%). "ratio" = dimensionless multiplier (e.g. 1.25 = +25%, 0.80 = -20%).
enum: percent, ratio
currency string Currency for specific (per-unit) rates; omitted/null for ad-valorem (optional)
effectiveDate string Effective date of the rate (YYYY-MM-DD)
appliesToComponentName string BOM component this snapshot applies to (for indirect tariffs); omitted/null for the direct/product-level tariff (optional). DLN-138 maps this to the component FK at persist time.
componentDrivers CostAnalysisComponentDriverDto[] Driver-level cost attributions (tariff, labor, energy, localization, overhead, etc.) for this analysis - accepts array of full objects, id strings, or id numbers
id* string Unique identifier for the component driver
costAnalysis any The parent cost analysis - accepts full object, id string, or id number
driver* string The driver type this row attributes cost to
enum: tariff, labor, energy, commodity, localization, freight, overhead, volume_discount, process_adjustment, supplier_purchasing_power
amount* number Cost impact of this driver, in minor currency units (cents). Positive means the driver increased cost; negative means it decreased it.
stdErr number Approximate standard error of `amount`, in minor currency units (cents).
source* string How `amount` was derived
enum: rate_snapshot, manual_adjustment, model
rateSnapshotId string FK to COST_ANALYSIS_RATE_SNAPSHOT.id when the driver was derived from an ingested rate. Stored as a plain uuid (no $ref) following the deferred-FK convention. Nullable (overhead drivers have source=manual_adjustment with no snapshot; synthesized tariff drivers use source=model with no snapshot).
appliesToComponentName string BOM component this driver applies to, when component-specific. Absent = driver applies at the cost-analysis / product level (v1 default for labor / energy / localization / overhead). DLN-138 maps to the component FK at persist time when present.
metadata object Driver-specific qualitative context (e.g. localization sub-factor breakdown).
referencePrices CostAnalysisReferencePriceDto[] Reference prices (historical/benchmark) compared against should-cost for this analysis (DLN-189). - accepts array of full objects, id strings, or id numbers
id* string Unique identifier for the reference price
costAnalysis any The parent cost analysis this reference price was used for - accepts full object, id string, or id number
price* number Reference price in minor currency units (e.g., cents), on a PER-UNIT basis — the price of one unit of the product, never a total for the analysis's quantity. A cost analysis's shouldCostAmount, by contrast, is a total for the run's quantity, so a consumer comparing the two must scale this by that quantity (the pipeline request's `quantity`, default 1); SAVINGS_OPPORTUNITY.amount already has that scaling applied and records which basis it used in periodScope. Holding every row per-unit is what lets a row be carried over into a later analysis at a different quantity without going stale. DLN-556.
currency* string ISO 4217 code of the denomination of `price`. A price paid entered in the analysis's reportingCurrency is stored in USD at the analysis's fxRate, with the entered amount kept exactly in presented.price. On an analysis reporting in a currency other than USD, a first-hand price paid in any other currency (USD included) is stored as entered, carries no presented twin, and is never used for a savings figure; a carried-over row and an estimate are USD and are presented at the rate.
presented any The price in the analysis's reportingCurrency, present exactly for the rows the analysis can compare against: a price paid entered in that currency carries the entered amount (kept verbatim when the row is carried over to a version in the same currency); a carried-over row or an estimate in USD carries round_half_up(price x fxRate). Null when the analysis reports in USD, and for a first-hand price paid in any other currency, which is never compared.
pricedDate string Date the price was paid or observed (YYYY-MM-DD); optional
source* string How this reference price was obtained
enum: user_provided, ai_estimate
sourceReferencePriceId string When this row was carried over (cloned) from a prior cost analysis in the same product lineage, the id of the reference-price row it was cloned from. Stored as a plain uuid (no $ref) following the deferred-FK convention. Null for freshly-provided or estimated references.
savingsOpportunities SavingsOpportunityDto[] Savings opportunities derived from this analysis (DLN-247). At most one per run. - accepts array of full objects, id strings, or id numbers
id* string Unique identifier for the savings opportunity
costAnalysis any The cost analysis that identified this opportunity - accepts full object, id string, or id number
productId* string Product the opportunity is for. Plain uuid (no $ref) following the deferred-FK convention (product lives in another service).
amount* number Savings amount in minor currency units (cents): reference price minus should-cost. Positive = savings; may be zero or negative when the should-cost is at or above the reference price.
currency* string ISO 4217 code of the denomination of `amount`: the analysis's base currency (USD), the same denomination as both operands of the gap.
presented any The gap in the analysis's reportingCurrency, taken between the presented reference price and the presented should-cost, so both operands are in one denomination. NOT `amount` x fxRate, and it does not always equal it: a reference price entered in the reporting currency keeps the typed amount exactly and derives its base, so the two columns differ by the rate applied to that half-cent of rounding — under a minor unit at a rate near 1, and tens of them at a rate in the hundreds. Join on one column or the other, never across the pair. Null when the analysis reports in USD.
lowestPriceGap number On an analysis priced from the product's purchases (ADR 0156): the lowest price paid per negotiation unit minus the should-cost, in minor units of `currency`. Null otherwise, and when the purchases have no lowest price.
highestPriceGap number On an analysis priced from the product's purchases: the highest price paid per negotiation unit minus the should-cost, in minor units of `currency`. Null otherwise, and when the purchases have no highest price.
quantityBought number On an analysis priced from the product's purchases: the negotiation units bought in the roll-up's period, unrounded (800 each is 66.67 packs of 12). Null otherwise.
savingsAtStake number On an analysis priced from the product's purchases: the savings at stake, `amount` (the gap per negotiation unit at the average price) × quantityBought, rounded half up, in minor units of `currency`. Null otherwise.
periodScope* string Basis the amount is expressed on
enum: per_unit, annual, total
status* string Lifecycle status; opportunities are created "open"
enum: open, closed
identifiedDate* string Date the opportunity was identified (the cost analysis date, YYYY-MM-DD)
comparisonBasis* string What the savings is measured against
enum: prices_paid, prior_analysis, external_benchmark, target_cost
comparisonReferenceId* string Id of the COST_ANALYSIS_REFERENCE_PRICE row this opportunity was computed against. Plain string (no $ref) per the ERD's non-polymorphic-FK convention.
assigneeUserId string User the opportunity is assigned to (deferred-FK to the user service). Not populated by the pipeline; set by a later workflow.
targetCloseDate string Target date to act on the opportunity. Populated by a later workflow.
notes string Free-text notes. Populated by a later workflow.
adjustments AdjustmentsDto[] Adjustments applied to the cost model beyond the industry-average ASM baseline. Records which factors shaped the analysis without capturing exact numeric deltas.
type* string Identifier for the kind of adjustment applied
enum: manufacturer_financials, yield_adjustment, compliance_overhead, setup_amortization, named_adjustment, tariff, indirect_tariff, volume_discount, supplier_purchasing_power
source* string Origin of this adjustment
enum: user_provided, ai_estimate, analyst_judgment, system, edgar_auto, operator_entry
label string Human-readable description of what was adjusted
metadata object Adjustment-specific qualitative context (no dollar amounts). For type="manufacturer_financials": may include - cogsRatioAdjusted (boolean) - profitMarginOverridden (boolean) - fiscalYear (integer): the FY the financials describe - companyFinancialsId (uuid): the specific company_financials row that fed this run, enabling audit even after the cache rolls to a newer filing - financialsSource ("edgar" | "manual" | "llm" | "dto"): where the financials came from for this run - sgaCompleteness ({found:[...], missing:[...]}): presence of each financial figure the SG&A selection reads — revenue, cogs, the sm/ga pair, R&D, and the combined SG&A line — plus the reported operating income, tracked here as drift-detection evidence even though DLN-913 margin derivation never reads it: the operating margin comes only from revenue, cogs and SG&A. `missing` is not a defect signal: a filer that legitimately never files a given tag (e.g. a decomposed filer with no combined line) reports it missing without anything being wrong. - operatingMarginSource ("edgar_sga" | "asm_industry_average"): which route produced the margin PAIR. "edgar_sga" — the manufacturer's own margins, the operating half reconstructed from an SG&A sum: `(revenue - cogs - sga) / revenue` (the name is a misnomer for the manual-DTO case too; kept for backward compatibility). This is the ONLY way a manufacturer's own operating margin can be adopted. DLN-913 reversed an earlier decision (PR #636) to prefer a filer's own reported `us-gaap:OperatingIncomeLoss`: we cannot know what a filer included when it calculated that figure, so we derive the margin ourselves and never adopt theirs, however it compares to our own gross margin. "edgar_operating_income" was that now-retired source value; it can no longer appear. "asm_industry_average" — BOTH margins came from the industry baseline. Since DLN-913 the manufacturer's gross and operating margin are adopted together or not at all, so this value means the whole pair is industry-sourced; before DLN-913 a run could carry the manufacturer's gross margin beside an industry or derived operating margin. - industryAverageReason ("no_margin_pair" | "implausible_gross_margin"): why the run fell back. Present only when operatingMarginSource is "asm_industry_average". "no_margin_pair" — financials reached the engine (at least one of COGS, SG&A or the GROSS margin was supplied — an operating margin alone does not emit this adjustment) but did not yield both margins, so neither was adopted; this covers a half pair whose present half is the gross margin, and a filing that resolved some figure but neither margin (e.g. SG&A with no revenue). "implausible_gross_margin" — the DLN-888 band withheld the whole set. A run with NO manufacturer financials at all (nothing supplied) emits no manufacturer_financials adjustment and so carries neither this key nor operatingMarginSource — the frontend's own `financialsStatus`/`financialsSource` fields on computeInputs are what explain that case, not this metadata; so does a run whose only supplied figure is an operating margin — absence of the adjustment does not by itself mean nothing was supplied. - llmCitations ([{field, url, note}]): present only when financialsSource == "llm" For type="tariff" (direct product-level import tariff): may include - importCountry (string) - countryOfOrigin (string) - ratePercent (number) For type="indirect_tariff" (tariffs on foreign-sourced BOM components): may include - components (object): per-component breakdown of the indirect tariff impact
meta object
404 Not found

User

User identity and profile records. Each user maps 1:1 to a Firebase Auth account.

GET /users/{id}

Get user by ID (scoped to caller membership)

Parameters

NameInTypeRequiredDescription
id path string<uuid> required

Responses

200
200 Response
FieldTypeDescription
data UserDto
id* string Unique identifier for the user
firebaseUid string Firebase Auth UID — links this record to the user's GCIP auth account. Unique across all non-null values. Server-set: for INTERNAL users, User.AfterCreate calls tenant.createUser() and writes the returned uid back to this row; for EXTERNAL (invited) users the uid is bound at first SSO sign-in via resolve-invite (DLN-266) and starts as null. Any value supplied on POST is rejected. Immutable after creation. Uniqueness on non-null values is enforced by a partial unique index (not a Cruddy-managed constraint) that the user service installs at boot; see services/user/src/bootstrap/install-unique-indexes.ts.
email* string User's email address. Unique across all users.
firstName* string User's first name
lastName* string User's last name
displayName* string Display name shown in the UI (may differ from firstName + lastName)
image string GCS filename for the user's profile picture
title string Job title or role at the user's organization
phone string Contact phone number
bio string Short biography or about text
userType* string Whether this user is a Dalinea employee (INTERNAL) or a client user (EXTERNAL). Immutable after creation.
enum: INTERNAL, EXTERNAL
status* string Lifecycle status of the user account
enum: ACTIVE, INACTIVE, SUSPENDED, DELETED
language* string Preferred language (ISO 639-1 two-character code, e.g. "en")
lastLogin string Timestamp of the user's most recent login
firebaseTenantId string GCIP tenant ID for the organization this user authenticates under. Present for tenant-scoped users (EXTERNAL, DALINEA_ANALYST); absent for platform-scoped INTERNAL users (DALINEA_ADMIN) who authenticate at the project level. Determines which GCIP tenant createUser targets.
meta object
404 Not found
PUT /users/{id}

Update user (scoped to caller membership)

Parameters

NameInTypeRequiredDescription
id path string<uuid> required

Request Body

UserDto
FieldTypeDescription
id* string Unique identifier for the user
firebaseUid string Firebase Auth UID — links this record to the user's GCIP auth account. Unique across all non-null values. Server-set: for INTERNAL users, User.AfterCreate calls tenant.createUser() and writes the returned uid back to this row; for EXTERNAL (invited) users the uid is bound at first SSO sign-in via resolve-invite (DLN-266) and starts as null. Any value supplied on POST is rejected. Immutable after creation. Uniqueness on non-null values is enforced by a partial unique index (not a Cruddy-managed constraint) that the user service installs at boot; see services/user/src/bootstrap/install-unique-indexes.ts.
email* string User's email address. Unique across all users.
firstName* string User's first name
lastName* string User's last name
displayName* string Display name shown in the UI (may differ from firstName + lastName)
image string GCS filename for the user's profile picture
title string Job title or role at the user's organization
phone string Contact phone number
bio string Short biography or about text
userType* string Whether this user is a Dalinea employee (INTERNAL) or a client user (EXTERNAL). Immutable after creation.
enum: INTERNAL, EXTERNAL
status* string Lifecycle status of the user account
enum: ACTIVE, INACTIVE, SUSPENDED, DELETED
language* string Preferred language (ISO 639-1 two-character code, e.g. "en")
lastLogin string Timestamp of the user's most recent login
firebaseTenantId string GCIP tenant ID for the organization this user authenticates under. Present for tenant-scoped users (EXTERNAL, DALINEA_ANALYST); absent for platform-scoped INTERNAL users (DALINEA_ADMIN) who authenticate at the project level. Determines which GCIP tenant createUser targets.

Responses

200
200 Response
FieldTypeDescription
data UserDto
id* string Unique identifier for the user
firebaseUid string Firebase Auth UID — links this record to the user's GCIP auth account. Unique across all non-null values. Server-set: for INTERNAL users, User.AfterCreate calls tenant.createUser() and writes the returned uid back to this row; for EXTERNAL (invited) users the uid is bound at first SSO sign-in via resolve-invite (DLN-266) and starts as null. Any value supplied on POST is rejected. Immutable after creation. Uniqueness on non-null values is enforced by a partial unique index (not a Cruddy-managed constraint) that the user service installs at boot; see services/user/src/bootstrap/install-unique-indexes.ts.
email* string User's email address. Unique across all users.
firstName* string User's first name
lastName* string User's last name
displayName* string Display name shown in the UI (may differ from firstName + lastName)
image string GCS filename for the user's profile picture
title string Job title or role at the user's organization
phone string Contact phone number
bio string Short biography or about text
userType* string Whether this user is a Dalinea employee (INTERNAL) or a client user (EXTERNAL). Immutable after creation.
enum: INTERNAL, EXTERNAL
status* string Lifecycle status of the user account
enum: ACTIVE, INACTIVE, SUSPENDED, DELETED
language* string Preferred language (ISO 639-1 two-character code, e.g. "en")
lastLogin string Timestamp of the user's most recent login
firebaseTenantId string GCIP tenant ID for the organization this user authenticates under. Present for tenant-scoped users (EXTERNAL, DALINEA_ANALYST); absent for platform-scoped INTERNAL users (DALINEA_ADMIN) who authenticate at the project level. Determines which GCIP tenant createUser targets.
meta object

Organization

Tenant organizations. Each owns a GCIP tenant and one or more email domains used for sign-in discovery.

GET /organizations/{id}

Get organization by ID (scoped to caller membership)

Parameters

NameInTypeRequiredDescription
id path string<uuid> required

Responses

200
200 Response
FieldTypeDescription
data OrganizationDto
id* string Unique identifier for the organization
name* string Display name of the organization
status* string Lifecycle status of the organization
enum: ACTIVE, ARCHIVED, SUSPENDED
legalEntityName string Full legal entity name (may differ from display name)
taxId string Tax identification number (EIN, VAT, etc.)
crmId string CRM system identifier for this organization
companyId string FK to the @company record linked to this organization
defaultCurrency string Default ISO 4217 currency code (e.g. USD, EUR)
fiscalYearStart* number Month in which the fiscal year starts (1 = January, 12 = December)
redMultiplier number Signal's RED threshold as a markup tolerance over the band (DLN-894): an item is RED when overRatio (paid / band high) exceeds it, GREEN otherwise. 1 is strict, and is what a stored null reads as. Changed only through PUT /organizations/:id/signal-threshold, by Dalinea staff; the generic update rejects it. Withheld from the warehouse: the verdict routes read it from this service per request (DLN-960), never from the replicated table.
dateFormat* string Preferred date display format for this organization
enum: MM/DD/YYYY, DD/MM/YYYY, YYYY-MM-DD
defaultTimeZone* string Default time zone identifier for this organization (e.g. America/New_York, America/Chicago, Europe/Berlin, Asia/Tokyo)
domains* string[] Email domains associated with this organization (e.g. ["acme.com", "acme-legacy.com"]). Used for tenant discovery. Must be unique across all organizations.
firebaseTenantId string Google Cloud Identity Platform (GCIP) tenant ID for this organization. Auto-generated by GCIP (not a UUID). Set after org creation by the Firebase Admin SDK event listener. Required for EXTERNAL user authentication.
microsoftTenantId string Azure AD (Microsoft Entra) directory/tenant ID for this organization's customer-managed, single-tenant Microsoft SSO app registration. When set, the frontend passes it as the Microsoft OAuth `tenant` custom parameter so sign-in targets the customer's tenant authority instead of the default `common` (which single-tenant apps reject, AADSTS50194). Leave unset for orgs on the shared multi-tenant Dalinea app.
meta object
404 Not found
GET /organizations

List organization documents

Parameters

NameInTypeRequiredDescription
microsoftTenantId query string[] optional Filter by a set of values (repeatable param)
firebaseTenantId query string[] optional Filter by a set of values (repeatable param)
defaultTimeZone query string[] optional Filter by a set of values (repeatable param)
dateFormat query string[] optional Filter by a set of values (repeatable param)
fiscalYearStart query string[] optional Filter by a set of values (repeatable param)
defaultCurrency query string[] optional Filter by a set of values (repeatable param)
companyId query string[] optional Filter by a set of values (repeatable param)
crmId query string[] optional Filter by a set of values (repeatable param)
taxId query string[] optional Filter by a set of values (repeatable param)
legalEntityName query string[] optional Filter by a set of values (repeatable param)
status query string[] optional Filter by a set of values (repeatable param)
name query string[] optional Filter by a set of values (repeatable param)
id query string[] optional Filter by a set of values (repeatable param)
microsoftTenantId[contains] query string optional Case-insensitive substring match
firebaseTenantId[contains] query string optional Case-insensitive substring match
defaultTimeZone[contains] query string optional Case-insensitive substring match
dateFormat[contains] query string optional Case-insensitive substring match
defaultCurrency[contains] query string optional Case-insensitive substring match
crmId[contains] query string optional Case-insensitive substring match
taxId[contains] query string optional Case-insensitive substring match
legalEntityName[contains] query string optional Case-insensitive substring match
status[contains] query string optional Case-insensitive substring match
name[contains] query string optional Case-insensitive substring match
count query boolean optional Include totalCount of unbounded results in response meta
related query string[] optional Include related entities. Omit for all, "false" for none, or specify field names (repeatable, supports dot notation)
sortOrder query string optional Sort direction (default: asc)
sortBy query string optional Field name to sort by
offset query number optional
limit query number optional

Responses

200
200 Response
FieldTypeDescription
data OrganizationDto[]
id* string Unique identifier for the organization
name* string Display name of the organization
status* string Lifecycle status of the organization
enum: ACTIVE, ARCHIVED, SUSPENDED
legalEntityName string Full legal entity name (may differ from display name)
taxId string Tax identification number (EIN, VAT, etc.)
crmId string CRM system identifier for this organization
companyId string FK to the @company record linked to this organization
defaultCurrency string Default ISO 4217 currency code (e.g. USD, EUR)
fiscalYearStart* number Month in which the fiscal year starts (1 = January, 12 = December)
redMultiplier number Signal's RED threshold as a markup tolerance over the band (DLN-894): an item is RED when overRatio (paid / band high) exceeds it, GREEN otherwise. 1 is strict, and is what a stored null reads as. Changed only through PUT /organizations/:id/signal-threshold, by Dalinea staff; the generic update rejects it. Withheld from the warehouse: the verdict routes read it from this service per request (DLN-960), never from the replicated table.
dateFormat* string Preferred date display format for this organization
enum: MM/DD/YYYY, DD/MM/YYYY, YYYY-MM-DD
defaultTimeZone* string Default time zone identifier for this organization (e.g. America/New_York, America/Chicago, Europe/Berlin, Asia/Tokyo)
domains* string[] Email domains associated with this organization (e.g. ["acme.com", "acme-legacy.com"]). Used for tenant discovery. Must be unique across all organizations.
firebaseTenantId string Google Cloud Identity Platform (GCIP) tenant ID for this organization. Auto-generated by GCIP (not a UUID). Set after org creation by the Firebase Admin SDK event listener. Required for EXTERNAL user authentication.
microsoftTenantId string Azure AD (Microsoft Entra) directory/tenant ID for this organization's customer-managed, single-tenant Microsoft SSO app registration. When set, the frontend passes it as the Microsoft OAuth `tenant` custom parameter so sign-in targets the customer's tenant authority instead of the default `common` (which single-tenant apps reject, AADSTS50194). Leave unset for orgs on the shared multi-tenant Dalinea app.
meta object
totalCount integer Unbounded result count. Only present when ?count=true is passed.

Feature Flags

Feature flag configuration and resolution.

GET /feature-flags/defaults

Get the feature flag registry with the defaults this environment serves

Every declared flag with the default an organization with no override row gets in this environment, and the metadata needed to present it: label, kind (rollout vs restriction), and any flags it requires to take effect. `description` is operator-facing and is served only to Dalinea-internal callers. Resolution endpoints return the flat name-to-boolean map for a specific organization or user instead.

Parameters

NameInTypeRequiredDescription
x-user-type header string optional Gateway-set INTERNAL/EXTERNAL user type. Server-injected — clients never supply it; the gateway strips inbound copies. Absent → treated as external, so `description` is withheld.

Responses

200
200 Response
FieldTypeDescription
data* object Flag name to its registry entry.
meta* object
type* string
enum: FeatureFlagDefaults

Imports

Upload files for sharing or to feed into batch workflows like cost analysis.

POST /imports/upload-url

Get a signed PUT URL for uploading an import file.

Validates the asserted MIME type against the registry for the given importType. Returns a fileLocation the client must echo back to POST /imports once the upload is complete.

Request Body

ImportUploadUrlRequestDto
FieldTypeDescription
originalFilename* string Original filename (used as the leaf of the object path).
mimeType* string Client-asserted MIME type. Validated against the requested import type's allowlist.
sizeBytes* number Client-asserted file size in bytes. Rejected if FMS limit exceeded.
importType string Import type the file is for. Picks the MIME allowlist and the upload size cap (DLN-920). Defaults to COST_ANALYSIS_IMPORT.
enum: COST_ANALYSIS_IMPORT, SIGNAL_IMPORT, TRANSACTION_IMPORT

Responses

201
201 Response
FieldTypeDescription
data ImportUploadUrlResponseDto
uploadUrl* string
fileLocation* string GCS path of the form {bucket}/{tenantId}/{uuid}/{filename}.
expiresAt* string
contentType* string MIME type the PUT must declare (echoed as Content-Type).
requiredHeaders* object Headers the client MUST send verbatim on the PUT for the V4 signature to validate. Always includes content-type; includes x-goog-content-length-range when a size cap is set. Send exactly these — omitting one yields a 403 SignatureDoesNotMatch.
meta object
POST /imports/preview

Preview the columns + first few rows of an uploaded file.

For workflow imports the UI uses this to build column mappings. Streams the file through FMS, parses up to maxRows rows, and returns them.

Request Body

PreviewRequestDto
FieldTypeDescription
fileLocation* string fileLocation returned from upload-url, of the form {bucket}/{tenantId}/{uuid}/{filename}.
maxRows number Max sample rows to return. Default 5, max 50.

Responses

200
200 Response
FieldTypeDescription
data PreviewResponseDto
columns* string[]
sampleRows* object[]
totalRowsEstimate number Best-effort total-rows count, when the parser can compute it.
meta object
403 fileLocation belongs to another tenant
GET /imports

List import documents

Parameters

NameInTypeRequiredDescription
failedRowCount query string[] optional Filter by a set of values (repeatable param)
productJobsSubmittedAt query string[] optional Filter by a set of values (repeatable param)
processedRowCount query string[] optional Filter by a set of values (repeatable param)
totalRowCount query string[] optional Filter by a set of values (repeatable param)
errorMessage query string[] optional Filter by a set of values (repeatable param)
rejectionReason query string[] optional Filter by a set of values (repeatable param)
approvalMode query string[] optional Filter by a set of values (repeatable param)
approvedAt query string[] optional Filter by a set of values (repeatable param)
approvedByUserId query string[] optional Filter by a set of values (repeatable param)
uploadedByUserId query string[] optional Filter by a set of values (repeatable param)
status query string[] optional Filter by a set of values (repeatable param)
type query string[] optional Filter by a set of values (repeatable param)
sizeBytes query string[] optional Filter by a set of values (repeatable param)
mimeType query string[] optional Filter by a set of values (repeatable param)
originalFilename query string[] optional Filter by a set of values (repeatable param)
fileLocation query string[] optional Filter by a set of values (repeatable param)
description query string[] optional Filter by a set of values (repeatable param)
name query string[] optional Filter by a set of values (repeatable param)
id query string[] optional Filter by a set of values (repeatable param)
errorMessage[contains] query string optional Case-insensitive substring match
rejectionReason[contains] query string optional Case-insensitive substring match
approvalMode[contains] query string optional Case-insensitive substring match
approvedByUserId[contains] query string optional Case-insensitive substring match
uploadedByUserId[contains] query string optional Case-insensitive substring match
status[contains] query string optional Case-insensitive substring match
type[contains] query string optional Case-insensitive substring match
mimeType[contains] query string optional Case-insensitive substring match
originalFilename[contains] query string optional Case-insensitive substring match
fileLocation[contains] query string optional Case-insensitive substring match
description[contains] query string optional Case-insensitive substring match
name[contains] query string optional Case-insensitive substring match
count query boolean optional Include totalCount of unbounded results in response meta
related query string[] optional Include related entities. Omit for all, "false" for none, or specify field names (repeatable, supports dot notation)
sortOrder query string optional Sort direction (default: asc)
sortBy query string optional Field name to sort by
offset query number optional
limit query number optional

Responses

200
200 Response
FieldTypeDescription
data ImportDto[]
id string Unique identifier for the import
name* string User-supplied display name for the import
description string Optional user-supplied description
fileLocation* string GCS path of the form "{bucket}/{tenantId}/{uuid}/{filename}"
originalFilename* string Filename as supplied by the user at upload time
mimeType* string Actual MIME type reported by GCS at create time
sizeBytes* number Actual size in bytes reported by GCS at create time
type* string What kind of import this is, which decides how its rows are used. COST_ANALYSIS_IMPORT is IMPACT, the should-cost/BOM pipeline, and the default for every caller that does not ask. SIGNAL_IMPORT is the deterministic paid-price-vs-band screen (DLN-503), available only to organizations holding the SIGNAL_ANALYSIS flag. TRANSACTION_IMPORT is a file of purchase lines (invoice or PO) mapped for the SIGNAL or IMPACT bulk route, available only to organizations holding the TRANSACTIONS flag. See src/imports/import-types.ts for the registry.
enum: COST_ANALYSIS_IMPORT, SIGNAL_IMPORT, TRANSACTION_IMPORT
status* string Import lifecycle status. PENDING_CONFIG until the mapping is complete. A valid mapping moves a TRANSACTION_IMPORT to SUMMARY_READY, where its transactions are stored and nothing runs, until the uploader submits it; every other type goes straight to PENDING_APPROVAL. A transaction import on the SIGNAL route that an admin approves is PROCESSING ("Screening") until the last of its products is screened, then COMPLETED, PARTIALLY_COMPLETED or FAILED (ADR 0154); one on the IMPACT route is APPROVED and runs nothing yet. Every other approved type is PROCESSING. REJECTED is final. See ADR 0149.
enum: PENDING_CONFIG, SUMMARY_READY, PENDING_APPROVAL, APPROVED, PROCESSING, COMPLETED, PARTIALLY_COMPLETED, FAILED, REJECTED
uploadedByUserId* string ID of the user that uploaded this import (from the request's auth context)
approvedByUserId string ID of the user who approved this import (set on approval): a Dalinea admin, or a client admin or owner. Unset for an AUTOMATIC dispatch.
approvedAt string Timestamp the import was approved
approvalMode string How the import was approved: INTERNAL (by Dalinea staff), EXTERNAL (by a client admin or owner, under the import-approvals flag for a type that runs the processor, or for any transaction import), or AUTOMATIC (dispatched by the system with no user action at all; never a transaction import). Unset while the import is still awaiting approval. The import-processor reads this to pick a row cap — anything that ran without Dalinea review is held to the tighter unattended limit.
enum: INTERNAL, EXTERNAL, AUTOMATIC
rejectionReason string The reason the admin gave when rejecting the import, shown to the uploader. Optional; unset when the admin gave none or the import was not rejected.
config object Type-specific configuration. Validated by the service layer per type.
errorMessage string Error message if the import failed during processing
totalRowCount number Total rows the processor identified in the file
processedRowCount number Number of rows the processor has finished (successfully or otherwise)
productJobsSubmittedAt string When the import processor finished submitting an approved IMPACT bulk transaction import's runs, one per product (ADR 0162). Null until then, and cleared on each approval. The import does not end before it is set.
failedRowCount number Number of rows that failed processing
meta object
totalCount integer Unbounded result count. Only present when ?count=true is passed.
POST /imports

Create an Import record from an uploaded file.

Parameters

NameInTypeRequiredDescription
x-feature-flags header string optional Gateway-resolved feature flags for this (organization, user). Server-injected — clients never supply it; the gateway strips inbound copies. Consulted when `type` is SIGNAL_IMPORT: absent or missing SIGNAL_ANALYSIS is a 403, for internal and external callers alike. Likewise TRANSACTIONS when `type` is TRANSACTION_IMPORT.

Request Body

CreateImportRequestDto
FieldTypeDescription
name* string
description string
fileLocation* string fileLocation returned from POST /imports/upload-url.
originalFilename* string
type string Which analysis framework this import runs under. Defaults to COST_ANALYSIS_IMPORT when omitted — every existing caller is unaffected. SIGNAL_IMPORT requires the organization to hold the SIGNAL_ANALYSIS flag; requesting it otherwise is a 403 (a capability gap, not a malformed request), for internal and external callers alike (SIGNAL_ANALYSIS is a rollout flag, not a restriction flag — see import-types.ts). TRANSACTION_IMPORT requires the TRANSACTIONS flag in the same way.
enum: COST_ANALYSIS_IMPORT, SIGNAL_IMPORT, TRANSACTION_IMPORT
config object Type-specific configuration, validated against `type`'s DTO (CostAnalysisImportConfig when `type` is omitted, SignalImportConfig for SIGNAL_IMPORT, TransactionImportConfig for TRANSACTION_IMPORT). A missing or incomplete columnMappings leaves the import PENDING_CONFIG; any other invalid value is refused with 400. A TRANSACTION_IMPORT config names its `route` (SIGNAL_2A or IMPACT_1B), sent here or with the first mapping; a missing or unknown route is refused with 400. `spendCubeExport: true` (default false) marks a file with no document key: document number and line number are then recommended, not required. A TRANSACTION_IMPORT mapping that leaves out a column its route requires is refused with 400 naming each missing column; any other incomplete or wrong-typed mapping parks it in PENDING_CONFIG. A TRANSACTION_IMPORT columnMappings may also carry `customFields`: custom field id (GET /custom-field) to column header, at most 50, each field once. A custom field never fills a required column. An id the organisation has no live custom field for passes this check, but the ingest that follows a valid mapping refuses it: the import records the error and stores no transactions.

Responses

201
201 Response
FieldTypeDescription
data ImportDto
id string Unique identifier for the import
name* string User-supplied display name for the import
description string Optional user-supplied description
fileLocation* string GCS path of the form "{bucket}/{tenantId}/{uuid}/{filename}"
originalFilename* string Filename as supplied by the user at upload time
mimeType* string Actual MIME type reported by GCS at create time
sizeBytes* number Actual size in bytes reported by GCS at create time
type* string What kind of import this is, which decides how its rows are used. COST_ANALYSIS_IMPORT is IMPACT, the should-cost/BOM pipeline, and the default for every caller that does not ask. SIGNAL_IMPORT is the deterministic paid-price-vs-band screen (DLN-503), available only to organizations holding the SIGNAL_ANALYSIS flag. TRANSACTION_IMPORT is a file of purchase lines (invoice or PO) mapped for the SIGNAL or IMPACT bulk route, available only to organizations holding the TRANSACTIONS flag. See src/imports/import-types.ts for the registry.
enum: COST_ANALYSIS_IMPORT, SIGNAL_IMPORT, TRANSACTION_IMPORT
status* string Import lifecycle status. PENDING_CONFIG until the mapping is complete. A valid mapping moves a TRANSACTION_IMPORT to SUMMARY_READY, where its transactions are stored and nothing runs, until the uploader submits it; every other type goes straight to PENDING_APPROVAL. A transaction import on the SIGNAL route that an admin approves is PROCESSING ("Screening") until the last of its products is screened, then COMPLETED, PARTIALLY_COMPLETED or FAILED (ADR 0154); one on the IMPACT route is APPROVED and runs nothing yet. Every other approved type is PROCESSING. REJECTED is final. See ADR 0149.
enum: PENDING_CONFIG, SUMMARY_READY, PENDING_APPROVAL, APPROVED, PROCESSING, COMPLETED, PARTIALLY_COMPLETED, FAILED, REJECTED
uploadedByUserId* string ID of the user that uploaded this import (from the request's auth context)
approvedByUserId string ID of the user who approved this import (set on approval): a Dalinea admin, or a client admin or owner. Unset for an AUTOMATIC dispatch.
approvedAt string Timestamp the import was approved
approvalMode string How the import was approved: INTERNAL (by Dalinea staff), EXTERNAL (by a client admin or owner, under the import-approvals flag for a type that runs the processor, or for any transaction import), or AUTOMATIC (dispatched by the system with no user action at all; never a transaction import). Unset while the import is still awaiting approval. The import-processor reads this to pick a row cap — anything that ran without Dalinea review is held to the tighter unattended limit.
enum: INTERNAL, EXTERNAL, AUTOMATIC
rejectionReason string The reason the admin gave when rejecting the import, shown to the uploader. Optional; unset when the admin gave none or the import was not rejected.
config object Type-specific configuration. Validated by the service layer per type.
errorMessage string Error message if the import failed during processing
totalRowCount number Total rows the processor identified in the file
processedRowCount number Number of rows the processor has finished (successfully or otherwise)
productJobsSubmittedAt string When the import processor finished submitting an approved IMPACT bulk transaction import's runs, one per product (ADR 0162). Null until then, and cleared on each approval. The import does not end before it is set.
failedRowCount number Number of rows that failed processing
meta object
400 `type` is not a known import type, or config is refused: a malformed value outside columnMappings, a key the type does not declare, or a TRANSACTION_IMPORT mapping missing a column its route requires (each missing column is named)
403 `type` is SIGNAL_IMPORT and this organization does not hold SIGNAL_ANALYSIS (or TRANSACTION_IMPORT without TRANSACTIONS) — a capability gap, not a malformed request; applies to internal and external callers alike
GET /imports/{id}

Get import by ID

Parameters

NameInTypeRequiredDescription
id path string<uuid> required
related query string[] optional Include related entities. Omit for all, "false" for none, or specify field names (repeatable, supports dot notation)

Responses

200
200 Response
FieldTypeDescription
data ImportDto
id string Unique identifier for the import
name* string User-supplied display name for the import
description string Optional user-supplied description
fileLocation* string GCS path of the form "{bucket}/{tenantId}/{uuid}/{filename}"
originalFilename* string Filename as supplied by the user at upload time
mimeType* string Actual MIME type reported by GCS at create time
sizeBytes* number Actual size in bytes reported by GCS at create time
type* string What kind of import this is, which decides how its rows are used. COST_ANALYSIS_IMPORT is IMPACT, the should-cost/BOM pipeline, and the default for every caller that does not ask. SIGNAL_IMPORT is the deterministic paid-price-vs-band screen (DLN-503), available only to organizations holding the SIGNAL_ANALYSIS flag. TRANSACTION_IMPORT is a file of purchase lines (invoice or PO) mapped for the SIGNAL or IMPACT bulk route, available only to organizations holding the TRANSACTIONS flag. See src/imports/import-types.ts for the registry.
enum: COST_ANALYSIS_IMPORT, SIGNAL_IMPORT, TRANSACTION_IMPORT
status* string Import lifecycle status. PENDING_CONFIG until the mapping is complete. A valid mapping moves a TRANSACTION_IMPORT to SUMMARY_READY, where its transactions are stored and nothing runs, until the uploader submits it; every other type goes straight to PENDING_APPROVAL. A transaction import on the SIGNAL route that an admin approves is PROCESSING ("Screening") until the last of its products is screened, then COMPLETED, PARTIALLY_COMPLETED or FAILED (ADR 0154); one on the IMPACT route is APPROVED and runs nothing yet. Every other approved type is PROCESSING. REJECTED is final. See ADR 0149.
enum: PENDING_CONFIG, SUMMARY_READY, PENDING_APPROVAL, APPROVED, PROCESSING, COMPLETED, PARTIALLY_COMPLETED, FAILED, REJECTED
uploadedByUserId* string ID of the user that uploaded this import (from the request's auth context)
approvedByUserId string ID of the user who approved this import (set on approval): a Dalinea admin, or a client admin or owner. Unset for an AUTOMATIC dispatch.
approvedAt string Timestamp the import was approved
approvalMode string How the import was approved: INTERNAL (by Dalinea staff), EXTERNAL (by a client admin or owner, under the import-approvals flag for a type that runs the processor, or for any transaction import), or AUTOMATIC (dispatched by the system with no user action at all; never a transaction import). Unset while the import is still awaiting approval. The import-processor reads this to pick a row cap — anything that ran without Dalinea review is held to the tighter unattended limit.
enum: INTERNAL, EXTERNAL, AUTOMATIC
rejectionReason string The reason the admin gave when rejecting the import, shown to the uploader. Optional; unset when the admin gave none or the import was not rejected.
config object Type-specific configuration. Validated by the service layer per type.
errorMessage string Error message if the import failed during processing
totalRowCount number Total rows the processor identified in the file
processedRowCount number Number of rows the processor has finished (successfully or otherwise)
productJobsSubmittedAt string When the import processor finished submitting an approved IMPACT bulk transaction import's runs, one per product (ADR 0162). Null until then, and cleared on each approval. The import does not end before it is set.
failedRowCount number Number of rows that failed processing
meta object
404 Not found
PATCH /imports/{id}

Partial-update name/description/config. Recomputes status from config.

Allowed only while the import is PENDING_CONFIG, SUMMARY_READY or PENDING_APPROVAL. config is merged onto the stored config one top-level key at a time: a key the body names (columnMappings, jobDefaults) replaces the stored value whole, and a key it omits is kept. Status is then recomputed: valid → PENDING_APPROVAL (SUMMARY_READY for a TRANSACTION_IMPORT, which stores its transactions and waits for POST /imports/{id}/submit), a missing or incomplete columnMappings → PENDING_CONFIG. A malformed value outside columnMappings, or a key the config does not declare, is refused with 400. So is a TRANSACTION_IMPORT columnMappings that leaves out a column its route requires: the response names each missing column.

Parameters

NameInTypeRequiredDescription
id path string<uuid> required
x-feature-flags header string optional Gateway-resolved feature flags for this (organization, user). Consulted when `type` changes to SIGNAL_IMPORT or TRANSACTION_IMPORT — see POST /imports.

Request Body

UpdateImportRequestDto
FieldTypeDescription
name string
description string
type string Change the analysis framework this import runs under. Only honored while the import is PENDING_CONFIG — any other status is a 409. Revalidates `config` against the new type's DTO and recomputes status; the stored config is NOT carried across a type change, so a patch that changes type without also sending config clears it and the import returns to PENDING_CONFIG to be re-mapped. SIGNAL_IMPORT requires the organization to hold the SIGNAL_ANALYSIS flag; requesting it otherwise is a 403 (a capability gap, not a malformed request), for internal and external callers alike. TRANSACTION_IMPORT requires the TRANSACTIONS flag in the same way.
enum: COST_ANALYSIS_IMPORT, SIGNAL_IMPORT, TRANSACTION_IMPORT
config object Merged onto the stored config one top-level key at a time: each key named here replaces the stored value whole, and a key left out is kept. null clears the config. The merge is within one type — when this patch also changes `type`, the value here becomes the whole config instead, since the stored one was validated against a different framework's DTO. The resulting status is recomputed from validity.

Responses

200
200 Response
FieldTypeDescription
data ImportDto
id string Unique identifier for the import
name* string User-supplied display name for the import
description string Optional user-supplied description
fileLocation* string GCS path of the form "{bucket}/{tenantId}/{uuid}/{filename}"
originalFilename* string Filename as supplied by the user at upload time
mimeType* string Actual MIME type reported by GCS at create time
sizeBytes* number Actual size in bytes reported by GCS at create time
type* string What kind of import this is, which decides how its rows are used. COST_ANALYSIS_IMPORT is IMPACT, the should-cost/BOM pipeline, and the default for every caller that does not ask. SIGNAL_IMPORT is the deterministic paid-price-vs-band screen (DLN-503), available only to organizations holding the SIGNAL_ANALYSIS flag. TRANSACTION_IMPORT is a file of purchase lines (invoice or PO) mapped for the SIGNAL or IMPACT bulk route, available only to organizations holding the TRANSACTIONS flag. See src/imports/import-types.ts for the registry.
enum: COST_ANALYSIS_IMPORT, SIGNAL_IMPORT, TRANSACTION_IMPORT
status* string Import lifecycle status. PENDING_CONFIG until the mapping is complete. A valid mapping moves a TRANSACTION_IMPORT to SUMMARY_READY, where its transactions are stored and nothing runs, until the uploader submits it; every other type goes straight to PENDING_APPROVAL. A transaction import on the SIGNAL route that an admin approves is PROCESSING ("Screening") until the last of its products is screened, then COMPLETED, PARTIALLY_COMPLETED or FAILED (ADR 0154); one on the IMPACT route is APPROVED and runs nothing yet. Every other approved type is PROCESSING. REJECTED is final. See ADR 0149.
enum: PENDING_CONFIG, SUMMARY_READY, PENDING_APPROVAL, APPROVED, PROCESSING, COMPLETED, PARTIALLY_COMPLETED, FAILED, REJECTED
uploadedByUserId* string ID of the user that uploaded this import (from the request's auth context)
approvedByUserId string ID of the user who approved this import (set on approval): a Dalinea admin, or a client admin or owner. Unset for an AUTOMATIC dispatch.
approvedAt string Timestamp the import was approved
approvalMode string How the import was approved: INTERNAL (by Dalinea staff), EXTERNAL (by a client admin or owner, under the import-approvals flag for a type that runs the processor, or for any transaction import), or AUTOMATIC (dispatched by the system with no user action at all; never a transaction import). Unset while the import is still awaiting approval. The import-processor reads this to pick a row cap — anything that ran without Dalinea review is held to the tighter unattended limit.
enum: INTERNAL, EXTERNAL, AUTOMATIC
rejectionReason string The reason the admin gave when rejecting the import, shown to the uploader. Optional; unset when the admin gave none or the import was not rejected.
config object Type-specific configuration. Validated by the service layer per type.
errorMessage string Error message if the import failed during processing
totalRowCount number Total rows the processor identified in the file
processedRowCount number Number of rows the processor has finished (successfully or otherwise)
productJobsSubmittedAt string When the import processor finished submitting an approved IMPACT bulk transaction import's runs, one per product (ADR 0162). Null until then, and cleared on each approval. The import does not end before it is set.
failedRowCount number Number of rows that failed processing
meta object
400 `type` is not a known import type, or config carries a malformed value outside columnMappings, a key the type does not declare, or a TRANSACTION_IMPORT mapping missing a column its route requires
403 `type` is changing to SIGNAL_IMPORT and this organization does not hold SIGNAL_ANALYSIS (or to TRANSACTION_IMPORT without TRANSACTIONS) — applies to internal and external callers alike
409 Import is not in an editable state, or a type change was attempted outside PENDING_CONFIG
DELETE /imports/{id}

Delete an Import and its GCS object. Rejected while PROCESSING or busy.

Parameters

NameInTypeRequiredDescription
id path string<uuid> required
hard query string required

Responses

204 Deleted
409 Cannot delete while PROCESSING, or while busy (an ingest is running); try again shortly
GET /imports/{id}/jobs

List child pipeline job rows for an import with their own status (paginated).

Parameters

NameInTypeRequiredDescription
id path string<uuid> required
pageSize query number optional Default 50, max 200
page query number optional Default 1

Responses

200
ImportJobsResponseDto
FieldTypeDescription
data* ImportJobItemDto[]
importJobId* string
pipelineJobId* string
rowIndex* number
rowData* object
submissionError string
pipelineStatus* string Live status from the Pipeline Service.
pipelineStep string
meta* ImportJobsResponseMetaDto
totalCount* number
aggregate* ImportJobsAggregateDto
total* number
completed* number
failed* number
inProgress* number
GET /imports/{id}/errors

List row-level errors for an import (paginated). Empty list when none.

Returns the failed job rows recorded during processing (rows with status "failed" and an error message). These cover both pre-submission and execution failures; live pipeline execution status is on GET /imports/:id/jobs.

Parameters

NameInTypeRequiredDescription
id path string<uuid> required
pageSize query number optional Default 50, max 200
page query number optional Default 1

Responses

200
ImportErrorsResponseDto
FieldTypeDescription
data* ImportRowErrorDto[]
rowIndex* number
rowData* object
errorMessage* string Why the row failed during processing.
meta* ImportErrorsResponseMetaDto
totalCount* number
404 Import not found
POST /imports/{id}/download-url

Sign a download URL for the import file. Tenant-scoped.

Parameters

NameInTypeRequiredDescription
id path string<uuid> required

Responses

200
200 Response
FieldTypeDescription
data ImportDownloadUrlResponseDto
downloadUrl* string
expiresAt* string
meta object
404 Import not found