curl --request POST \
--url https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start \
--header 'X-API-Key: <api-key>'import requests
url = "https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start"
headers = {"X-API-Key": "<api-key>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {'X-API-Key': '<api-key>'}};
fetch('https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"chunk_rows": 10000,
"chunks_done": 25,
"chunks_total": 25,
"completed_at": "2026-09-11T07:41:00Z",
"dry_run": true,
"job_id": "b2c3d4e5-f678-9012-abcd-ef2345678901",
"poll_url": "/products/api/v1/import/retailer-catalog/jobs/b2c3d4e5-f678-9012-abcd-ef2345678901",
"processed_rows": 248566,
"reclaim_unattributed": true,
"result": {
"dual_listed": 14300,
"listings_created": 247566,
"products_created": 233241,
"products_enriched": 14300,
"quarantine": [
{
"detail": "net_content is 117 characters, limit 100",
"raw_gtin": "3378872412345",
"reason": "field_too_long",
"row_number": 18342,
"source_reference": "2598765"
}
],
"quarantine_reasons": {
"field_too_long": 1
},
"quarantined": 1,
"rows_read": 248566
},
"source": "ulta_catalog",
"source_file": "ulta_catalog_2025-12.psv",
"staged_rows": 248566,
"started_at": "2026-09-11T07:10:00Z",
"status": "completed"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}Start or resume a catalog import job
Queue the staged rows for the server-side merge and return 202 with the job in queued. Poll GET .../jobs/{job_id} for progress — processed_rows advances every batch_size rows, chunks_done and result every chunk_rows — and for the outcome.
The merge runs as a sequential chain of chunk tasks of chunk_rows rows each, so no single worker task approaches its time limit. Calling this on a failed job resumes it where it stopped: every finished chunk keeps its rows and its share of the census, and so does every finished batch of the chunk that was in flight, so an interrupted run costs at most batch_size rows of repeated work rather than a chunk or a feed.
Returns 409 when the job is queued, running or completed — the first two already have a chain and a second would race it on one cursor, and the third already has its census — or when no rows are staged, because an empty run would report zeros that read like a clean import.
curl --request POST \
--url https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start \
--header 'X-API-Key: <api-key>'import requests
url = "https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start"
headers = {"X-API-Key": "<api-key>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {'X-API-Key': '<api-key>'}};
fetch('https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/products/api/v1/import/retailer-catalog/jobs/{job_id}/start")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"chunk_rows": 10000,
"chunks_done": 25,
"chunks_total": 25,
"completed_at": "2026-09-11T07:41:00Z",
"dry_run": true,
"job_id": "b2c3d4e5-f678-9012-abcd-ef2345678901",
"poll_url": "/products/api/v1/import/retailer-catalog/jobs/b2c3d4e5-f678-9012-abcd-ef2345678901",
"processed_rows": 248566,
"reclaim_unattributed": true,
"result": {
"dual_listed": 14300,
"listings_created": 247566,
"products_created": 233241,
"products_enriched": 14300,
"quarantine": [
{
"detail": "net_content is 117 characters, limit 100",
"raw_gtin": "3378872412345",
"reason": "field_too_long",
"row_number": 18342,
"source_reference": "2598765"
}
],
"quarantine_reasons": {
"field_too_long": 1
},
"quarantined": 1,
"rows_read": 248566
},
"source": "ulta_catalog",
"source_file": "ulta_catalog_2025-12.psv",
"staged_rows": 248566,
"started_at": "2026-09-11T07:10:00Z",
"status": "completed"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}Authorizations
Path Parameters
job_id returned when the job was opened.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$Response
Accepted
A job's current state. The same shape from open, append, start and poll.
Identifier of the job. Use it on the row, start and poll calls.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$staging — accepting rows; the run has not started. queued — start accepted, waiting for a worker. running — the merge is executing; chunks_done and result advance as it goes. completed — finished; result carries the whole feed's census. failed — the run gave up; error says why, result carries the census of the work that did finish, and POST .../start resumes from where it stopped.
Catalog source this run merges under.
Whether this run rolls back each batch.
Whether this run may claim unattributed fields.
Rows accepted into the job so far.
x >= 0Staged rows merged by one chunk task. The run is a sequential chain of these, so no single task approaches the worker's time limit. Set at open time from dry_run unless you named one: 10,000 for a dry run, 2,000 for a commit, because a commit does an order of magnitude more work per row.
x >= 1Chunks this job's staged rows divide into. Zero until rows are staged.
x >= 0Chunks fully merged and recorded. A resumed run continues from here, so these rows are never merged twice.
x >= 0Staged rows already merged. A floor, not an estimate: rows counted here are recorded and survive a restart. It advances within a chunk as well as between chunks — a chunk checkpoints every batch_size rows — so it keeps moving during the minutes a commit chunk takes, and is the field to watch to tell a slow run from a stopped one.
x >= 0Path to poll for this job's status and result.
Feed drop name recorded as listing provenance.
When the run began. Null before it starts.
When the run reached a terminal state.
Why the run failed. Null unless status is failed.
The engine's census. Empty until the first chunk finishes, then the running total for the chunks merged so far, and the whole feed's figures once status is completed — so read status, not this field, to decide whether a run is done. Carries rows_read, products_created / products_enriched / products_unchanged, listings_created / listings_updated, dual_listed (products another retailer's feed had already created — the cross-retailer overlap), duplicate_gtins_in_feed, quarantined with quarantine_reasons, brand and ingredient counts, field_decisions (per field, how each value was decided), reclaimable_fields, and quarantine — the refused rows themselves, capped at 500 entries with quarantined remaining the true total. errors is capped the same way, with errors_total as its true count. census is the same figures as human-readable lines.