Importing Components
Duro supports bulk-importing components from a spreadsheet through the GraphQL API. An import runs in two steps — you prepare an import to validate it, then start it to write the results — and you poll the resulting job for progress and per-row outcomes.
Imports are atomic (all-or-nothing). If any row fails, the entire import is rolled back and nothing is written to your library. A row that passed validation is not guaranteed to have been written — see Atomic behavior below.
Required Headers
All import operations require the library headers:
curl 'https://api.durohub.com/graphql' \
-H 'x-api-key: YOUR_API_KEY' \
-H 'x-organization: @your-org' \
-H 'x-library: @your-org/your-library' \
-H 'Content-Type: application/json' \
-d '{"query": "..."}'The Import Workflow
Imports live under the componentImport namespace on both Query and Mutation.
1. Prepare (validate)
prepare parses and validates your rows and returns a PreparedImportOutput describing what would happen — how many components would be created, how many rows are valid, and any validation errors. Nothing is written to the library at this stage.
mutation PrepareImport {
componentImport {
prepare(input: {
libraryId: "library-uuid"
fileId: "uploaded-file-uuid"
keyColumn: CPN
}) {
id
status
totalComponents
validCount
errorCount
newComponentCount
linkedToExistingCount
errors {
rowNumber
field
code
message
}
}
}
}The keyColumn (NAME, EID, or CPN) selects the identifier column used to match import rows to existing components for update mode. A row whose value resolves to exactly one live component is treated as an update of that component; otherwise it is a create.
Column mappings
prepare accepts an optional list of ColumnMappingInputs telling Duro which spreadsheet column feeds which field. Each mapping pairs a columnIndex (or column header) with a targetField. Besides the plain component fields, targetField supports prefixed families — attr: for custom attributes, hierarchy: for category hierarchy values, rel: for relationships, and src: for sourcing.
Sourcing (src:) targets
Map a column to a src: target to bring a component's source — its manufacturer part and the quote for it — in on the same row as the component itself:
targetField
Maps to
src:manufacturer
Manufacturer name for the source
src:mpn
Manufacturer part number
src:mfrDescription
Description on the manufacturer part
src:datasheet
Datasheet URL for the manufacturer part
src:distributor
Distributor name for the quote
src:dpn
Distributor part number
src:distDescription
Description on the quote
src:packageType
Package type on the quote (for example Tape & Reel)
src:packageQuantity
Units per package on the quote
src:minQuantity
Minimum order quantity on the quote
src:unitPrice
Price per unit
src:leadTime
Lead time for the quote
A few things to know about the sourcing columns:
Values are carried through as written.
"$1.00"and"1 Day"are kept as the text you supplied rather than normalized at mapping time, so failure reports can echo back exactly what was in the cell.Manufacturer, distributor, and package values stay as names. They are resolved to sourcing records later, when the import is applied.
A row with every sourcing cell blank yields no source. It is treated as a component-only row, not as an empty source.
One source per component row. Additional sources on continuation rows are not supported; each row carries at most one source.
src: mappings travel through the same prepare/start flow as the rest of the import: prepare parses them alongside the component columns and reports any row errors, and start records the parsed sourcing rows as part of the job it creates.
Descriptions, datasheets, and package types
The four descriptive sourcing targets have behavior worth knowing before you build a sheet around them:
Descriptions are length-limited, and over-long values are refused.
src:mfrDescriptionandsrc:distDescriptionare held to the same length limit as the sourcing API's owndescriptionfields. A cell that exceeds it fails its row rather than being silently truncated — a half-stored note is a worse outcome than a rejected one.src:datasheetmust be an absolute URL. The value is validated the same way the API validates a datasheet URL elsewhere, including requiring a protocol:https://example.com/ds.pdfis accepted, a bareexample.com/ds.pdfis not. When the manufacturer part is created, the URL becomes aDATASHEETdocument linked to that part, committed together with it.src:packageTypeis a lookup, never a create. Unlike manufacturer and distributor names — which are your own data and are created on demand — package types come from a fixed, shared list. The name is matched case-insensitively; a name Duro does not recognize fails only that row, and the error lists the valid spellings (the usual culprit is punctuation, such asTape and ReelforTape & Reel). A name that matches two package types differing only by case is refused rather than guessed at.Package types resolve before anything is written. An unrecognized package type fails its row without having created a manufacturer part first.
Manufacturer parts are create-only today. When a row's manufacturer and MPN match a manufacturer part that already exists, that part is reused as-is, so src:mfrDescription and src:datasheet take effect only on the row that first creates the part. Quote fields, including src:distDescription and src:packageType, are applied to the matched quote.
2. Start (execute)
Pass the id from the prepared import to start. This begins an ImportJob that stages and promotes the rows in a single transaction.
3. Poll the job
Poll componentImport.jobStatus until the job reaches a terminal state. Per-row results are available on terminal-state jobs.
An ImportJob moves through these statuses:
PARSING
Reading the source rows
STAGING_COMPONENTS
Writing components to the transaction
STAGING_LINKS
Writing assembly links to the transaction
PROMOTING
Committing the staged changes
COMPLETED
Every row was written successfully
ROLLING_BACK
A failure was hit; the transaction is being reverted
FAILED
The import was rejected; nothing was written
CANCELLING / CANCELLED
The import was cancelled before completion
Sourcing columns (src: targets)
A spreadsheet column can be mapped to a component field, to a library attribute (attr:<attribute-id>), or to a sourcing target using the src: prefix. Sourcing targets write the manufacturer part and distributor quote for the row's component instead of a field on the component itself — see Sourcing for the underlying model.
src:manufacturer
Manufacturer name
Identifies the manufacturer part together with src:mpn
src:mpn
Manufacturer part number
src:mfrDescription
Manufacturer part description
src:datasheet
Manufacturer part datasheet URL
src:distributor
Distributor name
Identifies the quote together with src:dpn
src:dpn
Distributor part number
src:distDescription
Quote description
src:packageType
Quote package type
Resolved by name — see below
src:packageQuantity
Quote package quantity
src:minQuantity
Quote minimum order quantity
src:unitPrice
Quote unit price
src:leadTime
Quote lead time
Only the targets in this list are recognized. Mapping a column to an unknown src: target is rejected when you prepare the import.
Package type resolution
src:packageType carries a human-readable package name (for example 0402 or SOIC-8), which is resolved against the package types Duro knows about. A value that matches nothing, or that matches more than one package type, fails with a dedicated error code — see Sourcing import errors.
Sourcing values are not validated when you prepare the import; the component rows are. Sourcing is applied after the components themselves are written, so a sourcing failure does not roll back the component import — check the job's row errors to see which sourcing values were rejected.
Atomic behavior (all-or-nothing)
Every import — create-only, update, or mixed — runs in a single transaction. Any error on any row (a create, an update, an assembly link, or a BOM reference) rolls back the whole import. On a rejected import nothing is written to your library, and no row is reported as CREATED or UPDATED.
This matters because import inserts components and links them as separate steps. Without atomicity, a parent row could fail while its children succeed, leaving orphaned children or a structurally broken BOM. All-or-nothing guarantees your library is never left in an incoherent state, and gives one predictable contract across create, update, and mixed sheets.
A row passing validation does not mean it was persisted. When the overall import is rejected because a different row failed, otherwise-valid rows are reported with the NOT_IMPORTED outcome. Always confirm the job reached COMPLETED (and check rowResultsSummary) before treating any row as written.
When an import is rejected, fix the offending row(s) reported in rowResults, then re-upload the sheet once — the rows that came back as NOT_IMPORTED were not partially applied and are safe to import again.
Row Outcomes
Each row's outcome is an ImportRowOutcome:
CREATED
A new component was created from this row
UPDATED
A matched component was updated because its data changed
UNCHANGED
The row equalled the existing component; a suppressed no-op
SKIPPED_DUPLICATE
The row duplicated another row in the same import and was skipped
FAILED_VALIDATION
The row failed validation (see errors)
FAILED_PROMOTION
The row was valid but failed while being committed (see errors)
NOT_IMPORTED
The row was otherwise valid but the import was rejected because another row failed, so it was rolled back and never written
CREATED, UPDATED, and UNCHANGED only appear when the whole job reaches COMPLETED. On a rejected import, the failing row(s) carry FAILED_VALIDATION or FAILED_PROMOTION and every other row carries NOT_IMPORTED.
Results Summary
rowResultsSummary (ImportRowResultsSummary) reports counts by outcome across the whole job. Every field is a non-null Int:
total
Total rows in the import
created
Rows that created a new component
updated
Rows that updated an existing component
unchanged
Matched rows that were no-ops
skippedDuplicate
Rows skipped as duplicates within the import
failedValidation
Rows that failed validation
failedPromotion
Rows that failed while being committed
notImported
Otherwise-valid rows that were rolled back because the import was rejected
Example: a rejected import
One bad row rejects the whole import. Here row 2 failed validation, and the remaining valid rows are reported as notImported rather than created:
Because created is 0 and status is FAILED, no component was written — including row 1 and row 3, which validated fine. Fix row 2 and re-upload.
Next Steps
Learn how to create and update Components individually
See Error Handling for general API error patterns
Last updated
Was this helpful?