# Bulk Validate Products

Checks a product file without saving anything, and returns the status of every row.
Send `multipart/form-data` with the file in a part named `file`: a `.csv` file (comma or semicolon separated) or an `.xlsx` workbook (only the first sheet is read), at most 10 MB and 5,000 data rows. The first row holds the column names. The required columns are `sku`, `name`, `selling_price`, `currency`, `barcode`, `hs_code`, `batch_management` and `expiry_management`. The column names of Quiqup's product template are accepted too. The optional `manufacturer_name` and `manufacturer_id` columns carry the customs manufacturer details; a blank cell keeps the stored value.
Each row comes back as `valid`, `invalid`, `duplicate` (the SKU already exists on your account) or `duplicate_in_file`, with its errors.
A file that cannot be read is rejected with `400` and a body of the form `{"error": "..."}`, not the usual error envelope.

Endpoint: POST /api/fulfilment/products/bulk/validate
Version: 1.0.0
Security: BearerAuth, OAuth2

## Security:

  - `BearerAuth` (unknown)
    http bearer

  - `OAuth2` (unknown)
    oauth2

## Request fields (multipart/form-data):

  - `file` (string, required)
    The product file: .csv or .xlsx, at most 10 MB and 5,000 data rows.

## Response 200:

  - `200` (unknown)
    Success response

## Response 200 fields (application/json):

  - `duplicate_rows` (integer)
    Rows whose SKU already exists on your account.

  - `invalid_rows` (integer)
    Rows with errors.

  - `rows` (array)
    One entry per data row.

  - `rows.cost_price` (number)
    The cost price on the row.

  - `rows.currency` (string)
    The currency on the row.

  - `rows.errors` (array)
    Why the row is invalid or failed.

  - `rows.name` (string)
    The product name on the row.

  - `rows.row_number` (integer)
    The row in the file. The header is row 1.

  - `rows.selling_price` (number)
    The selling price on the row.

  - `rows.sku` (string)
    The SKU on the row.

  - `rows.status` (string)
    What happened to the row. Validation returns valid, invalid, duplicate or duplicate_in_file; commit returns created, updated, skipped or failed.
    Enum: "valid", "invalid", "duplicate", "duplicate_in_file", "created", "updated", "skipped", "failed"

  - `total_rows` (integer)
    Data rows in the file.

  - `valid_rows` (integer)
    Rows that would be created.

  - `warnings` (array)
    Notes about the file, for example that only the first sheet of a workbook was read.

## Response 400:

  - `400` (unknown)
    The file could not be read: missing, too large, the wrong type, no data rows, more than 5,000 rows or a required column missing.

## Response 400 fields (application/json):

  - `error` (string)
    What was wrong with the file.

## Response 500:

  - `500` (unknown)
    The file could not be processed because of an error on Quiqup's side. Try again later.

## Response 500 fields (application/json):

  - `error` (string)
    What went wrong.

## Response 4XX:

  - `4XX` (unknown)
    Error response. See [Error responses](/docs/errors).

## Response 4XX fields (application/json):

  - `code` (string, required)
    The error code. It matches the HTTP status: invalid_argument and failed_precondition are 400, unauthenticated 401, permission_denied 403, not_found 404, already_exists 409, internal 500 and unavailable 503.
    Enum: "canceled", "unknown", "invalid_argument", "deadline_exceeded", "not_found", "already_exists", "permission_denied", "resource_exhausted", "failed_precondition", "aborted", "out_of_range", "unimplemented", "internal", "unavailable", "data_loss", "unauthenticated"

  - `details` (object)
    More about the error. Can be null.

  - `details.field_diffs` (array)
    When an inbound is refused because your product data and the warehouse's disagree, one entry per disagreement.

  - `details.field_diffs.field` (string)
    What disagrees, for example barcode_mismatch.

  - `details.field_diffs.ours` (string)
    The value Quiqup holds for the product.

  - `details.field_diffs.sku` (string)
    The SKU.

  - `details.field_diffs.warehouse` (string)
    The value the warehouse holds.

  - `details.fields` (array)
    For input errors, one entry per field that failed.

  - `details.fields.field` (string)
    The field that failed. Request validation names it by its path in the request, for example ShippingAddress.Phone or Items[0].UnitPrice, which is not always the JSON name.

  - `details.fields.message` (string)
    What was wrong. Request validation gives the rule that failed, for example required.

  - `details.reason` (string)
    A stable identifier for some failures, safe to branch on.

  - `details.request_id` (string)
    Identifies the request. Quote it when you contact support.

  - `details.type` (string)
    The kind of failure.
    Enum: "input", "not_found", "unauthorized", "forbidden", "integration", "internal"

  - `message` (string, required)
    A sentence describing the error.

## Response default:

  - `default` (unknown)
    Error response. See [Error responses](/docs/errors).

## Response default fields (application/json):

  - `code` (string, required)
    The error code. It matches the HTTP status: invalid_argument and failed_precondition are 400, unauthenticated 401, permission_denied 403, not_found 404, already_exists 409, internal 500 and unavailable 503.
    Enum: "canceled", "unknown", "invalid_argument", "deadline_exceeded", "not_found", "already_exists", "permission_denied", "resource_exhausted", "failed_precondition", "aborted", "out_of_range", "unimplemented", "internal", "unavailable", "data_loss", "unauthenticated"

  - `details` (object)
    More about the error. Can be null.

  - `details.field_diffs` (array)
    When an inbound is refused because your product data and the warehouse's disagree, one entry per disagreement.

  - `details.field_diffs.field` (string)
    What disagrees, for example barcode_mismatch.

  - `details.field_diffs.ours` (string)
    The value Quiqup holds for the product.

  - `details.field_diffs.sku` (string)
    The SKU.

  - `details.field_diffs.warehouse` (string)
    The value the warehouse holds.

  - `details.fields` (array)
    For input errors, one entry per field that failed.

  - `details.fields.field` (string)
    The field that failed. Request validation names it by its path in the request, for example ShippingAddress.Phone or Items[0].UnitPrice, which is not always the JSON name.

  - `details.fields.message` (string)
    What was wrong. Request validation gives the rule that failed, for example required.

  - `details.reason` (string)
    A stable identifier for some failures, safe to branch on.

  - `details.request_id` (string)
    Identifies the request. Quote it when you contact support.

  - `details.type` (string)
    The kind of failure.
    Enum: "input", "not_found", "unauthorized", "forbidden", "integration", "internal"

  - `message` (string, required)
    A sentence describing the error.

