Importing products from CSV
Adding products one at a time is the right shape for a handful of changes. A
whole catalog is a different job, and POST /api/v1/products/import does it in
one request: a CSV of up to 500 products, validated as a unit and written in a
single transaction that also records the background indexing those products
need before they can match.
Use the import when you are onboarding a catalog, taking on a new manufacturer
partner’s product line, or reconciling what Prokure holds against the
spreadsheet you already maintain. Use
Adding a product for a single
product, and PATCH for a single edit. The import has no specs column, so a
product whose specifications matter still needs one API call or one portal edit
after it lands.
The template
Section titled “The template”curl "https://app.prokure.ca/api/v1/products/import/template" \ -H "Authorization: Bearer $PROKURE_API_KEY" \ -o product-import-template.csvRequires profile:read. It returns a text/csv body: the header row plus one
example row.
oem_partner,name,model,description,commodity_codes,rep_status,source_urlVertex Optronics,Thermal Monocular,VX-300,"Handheld thermal imager, 640x480 sensor, 50 Hz refresh",5855;5855-20,carried,https://example.com/products/vx-300All seven columns have to be present. Their order does not matter, and header
cells are matched without regard to case or surrounding whitespace, so a
spreadsheet that exports OEM_Partner is read the same as one that exports
oem_partner.
The file has to be UTF-8. A byte-order mark is fine, and so is any accented or
non-Latin text the encoding covers, but any other encoding is refused: a
UTF-16 or Windows-1252 export is rejected with
file is not UTF-8 encoded; save the spreadsheet as CSV UTF-8 rather than
imported with its accented characters mangled. In Excel that means saving as
CSV UTF-8, not plain CSV.
Columns
Section titled “Columns”| Column | Required | Rule |
|---|---|---|
oem_partner |
Yes | The manufacturer partner’s name, 1 to 200 characters. Matched against your existing partners without regard to case; a name you do not have yet is created. |
name |
Yes | The product name, 1 to 200 characters. |
model |
No | Up to 100 characters. Leave it blank for a product with no model. |
description |
No | Up to 4000 characters. This is what a solicitation’s requirements are compared against, so it carries most of the matching weight. |
commodity_codes |
No | Up to 50 codes separated by ;, each 1 to 50 characters. |
rep_status |
No | carried or catalog, read without regard to case. Blank means carried. |
source_url |
No | An http or https URL. |
A cell that breaks one of those rules is reported with the rule as its message:
| Column | Message |
|---|---|
oem_partner |
oem_partner is required (1–200 characters) |
name |
name is required (1–200 characters) |
model |
model must be at most 100 characters |
description |
description must be at most 4000 characters |
commodity_codes |
commodity_codes: at most 50 codes, each 1–50 characters, separated by ; |
rep_status |
rep_status must be carried or catalog |
source_url |
source_url must be an http or https URL |
A header problem is reported on row 1, against the column it concerns:
missing required column for one of the seven that is absent, unknown column
for a column that is not one of the seven, and duplicate column for one that
appears twice.
Row numbers
Section titled “Row numbers”The header is row 1 and the first product is row 2, so the numbers in an issue list are the row numbers your spreadsheet shows. A description quoted across several lines still reports the line its row starts on, and blank lines are skipped without shifting the count.
Dry run first
Section titled “Dry run first”dry_run=true runs the whole validation and reports what the import would do,
without writing anything:
curl -X POST "https://app.prokure.ca/api/v1/products/import?dry_run=true" \ -H "Authorization: Bearer $PROKURE_API_KEY" \Requires profile:write. The request is multipart/form-data with one file
field. A valid file comes back as a preview:
{ "dry_run": true, "rows": 128, "partners_to_create": ["Vertex Optronics", "Halden Marine", "Kestrel Instruments"], "partners_to_reauthorize": ["Cascade Fabrication"], "products_to_create": 112, "products_to_update": 16}| Field | Meaning |
|---|---|
rows |
Data rows in the file, header excluded. |
partners_to_create |
The names of the manufacturer partners the import would create, spelled as the file spells them. |
partners_to_reauthorize |
The names of partners you already have but have removed from your company profile, spelled as your catalog already spells them. |
products_to_create |
Rows that do not match a product you already have. |
products_to_update |
Rows whose partner, name and model already match one. |
The two partner fields are lists of names rather than counts, so a preview shows you exactly which partners a file is about to introduce. A name you did not expect to see there is usually a typo in the spreadsheet: it reads as a new partner rather than as a row under an existing one.
Read products_to_update before you commit. Every row it counts is about to be
overwritten from the file on the terms in Re-importing. A
number larger than you expected usually means the file names products you have
already onboarded, not that the import is wrong.
All or nothing
Section titled “All or nothing”An import either applies in full or changes nothing. Any of these refuses the
whole file with 422 invalid_import:
- a file that is not UTF-8
- a header column missing, unknown, or repeated
- any cell that breaks its column’s rule
- the same partner, name and model appearing on two rows, reported on the
second as
duplicates row 12naming the first. The partner is compared without regard to case, and the name and model exactly, which is the identity your catalog itself enforces - more than 500 rows, reported on the first row past the cap as
at most 500 rows per import
The body lists what to fix:
{ "error": "invalid_import", "message": "3 issue(s) in the file; nothing was imported", "requestId": "req-9c2", "issues": [ { "row": 14, "column": "rep_status", "message": "rep_status must be carried or catalog" }, { "row": 21, "column": "source_url", "message": "source_url must be an http or https URL" }, { "row": 37, "column": null, "message": "duplicates row 12" } ], "issue_count": 3}An encoding failure is decided before anything is parsed, so it arrives alone, on row 1, as the file’s only issue. Everything else is checked across the whole file at once.
issues carries at most the first 50 problems; issue_count is how many there
really are, and it is the number the message quotes, so a file with 62
problems reports 62 while listing 50. A column of null means the problem is
the row itself rather than one of its cells, which is what a duplicate row and
the 500-row cap both report. Fix the file and send it again: nothing was
written, so there is no half-finished import to clean up first.
Importing
Section titled “Importing”Drop dry_run to commit:
curl -X POST "https://app.prokure.ca/api/v1/products/import" \ -H "Authorization: Bearer $PROKURE_API_KEY" \{ "dry_run": false, "partners_created": 3, "partners_reauthorized": 1, "products_created": 112, "products_updated": 16, "profile_changes_applied": 4, "profile_changes_failed": 0}Partners and products are written in one transaction, so a failure anywhere
leaves your catalog exactly as it was. profile_changes_applied counts the
entries the import wrote to your profile’s change history, one for each
manufacturer in the file that was missing from manufacturer_partners on your
company profile: the four above are the three created partners and the one
re-authorized, none of which were on the profile beforehand.
Every import checks all of the file’s manufacturers against the profile, not only the ones it created that run. A name that is already there is left alone, and a name that should be there but is not gets added by the next import that mentions it.
That reconciliation is also the repair path when the profile half goes wrong.
It runs after the catalog transaction has committed, so your products are in
either way, and a name that fails to reach the profile is counted in
profile_changes_failed rather than turned into an error: the response is
still a 200 reporting everything that was imported. A non-zero
profile_changes_failed is worth acting on, because manufacturer_partners is
one of the things Prok scores against, and the action is simply to import the
same file again.
| Field | Meaning |
|---|---|
partners_created |
Partner names in the file that you did not have. |
partners_reauthorized |
Partners you had removed from your company profile, added back by this import. |
products_created |
Rows that did not match an existing product. |
products_updated |
Rows that matched one, and overwrote it. |
profile_changes_applied |
Entries written to your profile’s change history, one per manufacturer in the file that was missing from manufacturer_partners and got added. A name already on the profile is not added twice, so this can come back lower than partners_created plus partners_reauthorized, and it can also count a partner this import neither created nor re-authorized. |
profile_changes_failed |
Manufacturers from the file that could not be added to your company profile. The products themselves were imported; importing the same file again repairs the profile. Normally 0. |
Re-importing
Section titled “Re-importing”A product is matched on its partner, name and model, which is the same identity
POST /api/v1/products refuses a duplicate on. A row that matches an existing
product updates it rather than creating a second one, so re-importing a
corrected spreadsheet is a supported way to work.
What that overwrite covers is worth being precise about, because a blank cell is an instruction, not an omission:
| Column | On a re-import |
|---|---|
description |
Overwritten from the file. A blank cell clears the stored description. |
commodity_codes |
Overwritten from the file. A blank cell clears the stored codes. |
rep_status |
Overwritten from the file. A blank cell sets carried. |
source_url |
Overwritten from the file. A blank cell clears the stored URL. |
specs |
Preserved. The file has no column for it. |
status |
Preserved. A discontinued product stays discontinued after a re-import. |
The file is authoritative for everything it carries, so build it from what you
currently hold rather than from only the rows you meant to change.
GET /api/v1/products lists the catalog as it stands. A file assembled from a
handful of edited rows, with the other columns left empty, clears those columns
on every product it touches.
status staying put is the deliberate exception. Discontinuing a product is a
decision you made about your catalog, and a re-import of the same product line
must not quietly bring it back into scoring. Reactivate one with
PATCH /api/v1/products/{id} when you mean to.
Partners and the change history
Section titled “Partners and the change history”A partner name in the file is matched against your existing partners without
regard to case, and the spelling you already have on file is the one kept, so
vertex optronics in a spreadsheet does not rename Vertex Optronics in your
catalog.
The same rule settles a file that spells a new partner two ways. Rows saying
Halden Marine and halden marine are one partner, not two, and the spelling
on the first of those rows is the one created and used for every row under it.
A file whose casing is inconsistent therefore imports cleanly; it just inherits
whichever spelling happens to come first, so it is worth spotting in the
dry run’s partners_to_create before you commit.
A name you do not have is created, and added to manufacturer_partners on your
company profile through the ordinary change history, with source set to
portal_import and shown in the portal as You — catalog import. Those
entries are revertible on the usual terms, the same as a partner you added by
hand. A partner you had removed from your profile is re-authorized and added
back, rather than blocking the import: an import is a clear enough statement
that you represent the partner again.
See Company profile for the history and revert rules those entries land in.
Indexing and re-scoring
Section titled “Indexing and re-scoring”The transaction that writes the catalog also records two pieces of background
work: the indexing the imported products need, and one re-score for the whole
catalog change rather than one per product. Nothing is indexed while the
request is open, so a 200 means the catalog is saved and that work is durably
recorded, not that indexing has finished. Indexing runs in the background in
bounded batches, and the imported products count toward matching once it
finishes. The re-scored results reach you in your next digest rather than in an
email of their own, exactly like a company-profile edit.
In the portal
Section titled “In the portal”The Product catalog page under Settings has an Import button. It opens a dialog that takes you through the same three steps: choose a file, read the dry-run preview, then import. A valid file previews as the counts above; an invalid one shows the issue table, with the row, the column and the message for each problem, so the file can be fixed and re-chosen without leaving the dialog. The dialog also links the template.
After an import it lists the same counts, and warns when
profile_changes_failed is not zero, telling you how many manufacturers did
not reach your company profile and to import the file again to repair it.
Limits
Section titled “Limits”- One file per request, a
.csvof at most 2 MB and at most 500 data rows. A larger catalog goes in as several files. - Cell limits are the catalog’s limits: name up to 200 characters, model up to 100, description up to 4000, up to 50 commodity codes of up to 50 characters each.
- 50 issues per response.
issue_countreports the true total when there are more. - 10 requests per minute on the import and 60 per minute on the template, counted per client IP like every other route. See Rate limits and errors.
Errors
Section titled “Errors”Four failures are specific to this route:
| Status | error |
Meaning |
|---|---|---|
400 |
missing_file |
The request carried no file field. |
413 |
file_too_large |
The file is over 2 MB, or the request carried more multipart parts than the route accepts. |
415 |
unsupported_type |
The file is not a .csv. |
422 |
invalid_import |
The file did not validate. Nothing was written. |
The usual 401, 403 and 429 apply on top of them, on the same terms as
every other route.
Related
Section titled “Related”- API reference: full request and response schemas.
- Product catalog: partners, products,
rep_statusandstatus, and how the catalog feeds matching. - Company profile: the change history and revert rules a partner addition lands in.
- Editing your company profile: manufacturer partners on the profile, and the partner-removal cascade.
- Rate limits and errors.
- Scopes explained.