#Import jobs
GET /import-jobs
List the organization's CSV import jobs, newest first. Poll this or the single-job endpoint to follow an import's progress.
Requires the imports:read permission.
Query Parameters
limitnumberThe number of records to return.
cursorstringA pagination cursor. Fetch the next page by passing the nextCursor value from the previous response.
sortcreatedAt | -createdAtThe sort order. Prefix a field with - to sort descending. Defaults to -createdAt.
filterstringA JSON-encoded filter document, for example {"createdAt":{"$gte":"2025-01-01T00:00:00Z"}}. Filterable fields: id, type, status, createdAt. See Filtering for the syntax.
curl \
"https://api.cascade.dev/import-jobs" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { getImportJobs } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getImportJobs()require "net/http"
uri = URI("https://api.cascade.dev/import-jobs")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
req = Request(
"https://api.cascade.dev/import-jobs",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"fileUrl": "string",
"type": "customers",
"format": "cascade",
"status": "pending",
"totalCount": 0,
"processedCount": 0,
"errors": ["..."],
"sendReviewRequests": true,
"earnLoyalty": true,
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}
],
"nextCursor": "string"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the import job."
},
"userId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The user who started the import."
},
"fileUrl": {
"type": "string",
"description": "A signed URL to download the source file the import ran against."
},
"type": {
"type": "string",
"enum": ["customers", "products", "orders", "wishlists", "reviews", "loyalty-members"],
"description": "What the file contains, which decides the columns the importer expects."
},
"format": {
"type": "string",
"enum": [
"cascade",
"loyaltylion",
"smile",
"rivo",
"yotpo",
"judgeme",
"okendo",
"stamped",
"loox",
"swym",
"wishlistking",
"growave",
"wishlisthero"
],
"description": "Whose column names the file uses. Anything other than `cascade` is remapped to Cascade's columns before the import runs."
},
"status": {
"type": "string",
"enum": ["pending", "processing", "completed", "failed", "cancelled"],
"description": "Where the import has got to."
},
"totalCount": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "How many rows the file holds, once it has been counted."
},
"processedCount": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "How many rows have been imported so far."
},
"errors": {
"type": "array",
"items": {
"type": "object",
"properties": {
"row": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "The 1-based row in the CSV that was skipped, or null if the problem stopped the whole file."
},
"message": {
"type": "string",
"description": "What went wrong with that row."
},
"severity": {
"description": "`error` for something the import could not handle, `notice` for something it handled but wants to tell you about, such as a column a competitor format does not carry. Absent means `error`.",
"type": "string",
"enum": ["error", "notice"]
}
},
"required": ["row", "message"],
"additionalProperties": false
},
"description": "Rows the importer could not handle, and notices about the file as a whole. An import that skipped rows still completes, so check this even on a successful job."
},
"sendReviewRequests": {
"type": "boolean",
"description": "Whether importing these orders also queues a review-request survey for each one, where its flow's trigger matches."
},
"earnLoyalty": {
"type": "boolean",
"description": "Whether orders created by this import earn loyalty points and stamps. Off by default, so importing history never mints points."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the import was started."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the import last made progress."
}
},
"required": [
"id",
"userId",
"fileUrl",
"type",
"format",
"status",
"totalCount",
"processedCount",
"errors",
"sendReviewRequests",
"earnLoyalty",
"createdAt",
"updatedAt"
],
"additionalProperties": false
},
"description": "The page of records, in the requested sort order."
},
"nextCursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Pass this back as `cursor` to fetch the next page. Null once the last page has been returned."
}
},
"required": ["data", "nextCursor"],
"additionalProperties": false
}POST /import-jobs
Queue a CSV import. Upload the file first with POST /files/upload-url using private access, then pass the key it returns. The import runs in the background, so poll the job to see how it is going. Only session-authenticated requests can start one, since an import is attributed to a user.
Requires the imports:write permission.
Costs 10 rate limit tokens instead of the usual one. See Rate limits.
Body Parameters
fileKeystringrequiredThe storage key returned by POST /files/upload-url for the uploaded CSV.
typecustomers | products | orders | wishlists | reviews | loyalty-membersrequiredWhat the file contains, which decides the columns the importer expects.
formatcascade | loyaltylion | smile | rivo | yotpo | judgeme | okendo | stamped | loox | swym | wishlistking | growave | wishlistheroWhose column names the file uses. Defaults to cascade, which is the column set the guides document. A competitor format is remapped to Cascade's columns before the import runs, and the file is refused if its columns do not match that format.
sendReviewRequestsbooleanFor an orders import, whether to also queue a review-request survey for each imported order whose flow's trigger matches. Ignored for other types.
earnLoyaltybooleanFor an orders import, whether imported orders earn loyalty points and stamps. Off by default, so importing history never mints points. Ignored for other types.
curl \
-X POST \
"https://api.cascade.dev/import-jobs" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"fileKey": "string",
"type": "customers",
"format": "cascade",
"sendReviewRequests": true,
"earnLoyalty": true
}'import { client } from "@cascade-commerce/api/admin/client"
import { postImportJobs } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postImportJobs({
body: {
fileKey: "string",
type: "customers",
format: "cascade",
sendReviewRequests: true,
earnLoyalty: true,
},
})require "net/http"
uri = URI("https://api.cascade.dev/import-jobs")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
req["Content-Type"] = "application/json"
req.body = <<~JSON
{
"fileKey": "string",
"type": "customers",
"format": "cascade",
"sendReviewRequests": true,
"earnLoyalty": true
}
JSON
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
body = """{
"fileKey": "string",
"type": "customers",
"format": "cascade",
"sendReviewRequests": true,
"earnLoyalty": true
}"""
req = Request(
"https://api.cascade.dev/import-jobs",
data=body.encode(),
headers={
"Authorization": "Bearer sk_YOUR_SECRET_KEY",
"Content-Type": "application/json",
},
)
with urlopen(req) as res:
print(json.load(res)){
"importJobId": "550e8400-e29b-41d4-a716-446655440000"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"importJobId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The ID of the queued import job."
}
},
"required": ["importJobId"],
"additionalProperties": false,
"description": "A handle you can poll for progress."
}GET /import-jobs/{id}
Get a single import job by ID, including its progress counts and any rows it skipped.
Requires the imports:read permission.
Path Parameters
idstring (uuid)requiredThe ID of the import job.
curl \
"https://api.cascade.dev/import-jobs/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { getImportJobsById } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getImportJobsById({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
})require "net/http"
uri = URI("https://api.cascade.dev/import-jobs/550e8400-e29b-41d4-a716-446655440000")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
req = Request(
"https://api.cascade.dev/import-jobs/550e8400-e29b-41d4-a716-446655440000",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"id": "550e8400-e29b-41d4-a716-446655440000",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"fileUrl": "string",
"type": "customers",
"format": "cascade",
"status": "pending",
"totalCount": 0,
"processedCount": 0,
"errors": [
{
"row": 0,
"message": "string",
"severity": "error"
}
],
"sendReviewRequests": true,
"earnLoyalty": true,
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the import job."
},
"userId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The user who started the import."
},
"fileUrl": {
"type": "string",
"description": "A signed URL to download the source file the import ran against."
},
"type": {
"type": "string",
"enum": ["customers", "products", "orders", "wishlists", "reviews", "loyalty-members"],
"description": "What the file contains, which decides the columns the importer expects."
},
"format": {
"type": "string",
"enum": [
"cascade",
"loyaltylion",
"smile",
"rivo",
"yotpo",
"judgeme",
"okendo",
"stamped",
"loox",
"swym",
"wishlistking",
"growave",
"wishlisthero"
],
"description": "Whose column names the file uses. Anything other than `cascade` is remapped to Cascade's columns before the import runs."
},
"status": {
"type": "string",
"enum": ["pending", "processing", "completed", "failed", "cancelled"],
"description": "Where the import has got to."
},
"totalCount": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "How many rows the file holds, once it has been counted."
},
"processedCount": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "How many rows have been imported so far."
},
"errors": {
"type": "array",
"items": {
"type": "object",
"properties": {
"row": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "The 1-based row in the CSV that was skipped, or null if the problem stopped the whole file."
},
"message": {
"type": "string",
"description": "What went wrong with that row."
},
"severity": {
"description": "`error` for something the import could not handle, `notice` for something it handled but wants to tell you about, such as a column a competitor format does not carry. Absent means `error`.",
"type": "string",
"enum": ["error", "notice"]
}
},
"required": ["row", "message"],
"additionalProperties": false
},
"description": "Rows the importer could not handle, and notices about the file as a whole. An import that skipped rows still completes, so check this even on a successful job."
},
"sendReviewRequests": {
"type": "boolean",
"description": "Whether importing these orders also queues a review-request survey for each one, where its flow's trigger matches."
},
"earnLoyalty": {
"type": "boolean",
"description": "Whether orders created by this import earn loyalty points and stamps. Off by default, so importing history never mints points."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the import was started."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the import last made progress."
}
},
"required": [
"id",
"userId",
"fileUrl",
"type",
"format",
"status",
"totalCount",
"processedCount",
"errors",
"sendReviewRequests",
"earnLoyalty",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The requested import job."
}GET /import-jobs/{id}/records
List the records an import wrote, in file order. A record deleted since the import keeps its place in the list and comes back with deleted set, so the history stays readable.
Requires the imports:read permission.
Path Parameters
idstring (uuid)requiredThe ID of the import job.
Query Parameters
limitnumberThe number of records to return.
cursorstringA pagination cursor. Fetch the next page by passing the nextCursor value from the previous response.
sortcreatedAt | -createdAtThe sort order. Prefix a field with - to sort descending. Defaults to createdAt.
filterstringA JSON-encoded filter document, for example {"createdAt":{"$gte":"2025-01-01T00:00:00Z"}}. Filterable fields: action, recordTable. See Filtering for the syntax.
curl \
"https://api.cascade.dev/import-jobs/550e8400-e29b-41d4-a716-446655440000/records" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@cascade-commerce/api/admin/client"
import { getImportJobsByIdRecords } from "@cascade-commerce/api/admin"
client.setConfig({
baseUrl: "https://api.cascade.dev",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getImportJobsByIdRecords({
path: { id: "550e8400-e29b-41d4-a716-446655440000" },
})require "net/http"
uri = URI("https://api.cascade.dev/import-jobs/550e8400-e29b-41d4-a716-446655440000/records")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer sk_YOUR_SECRET_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.bodyimport json
from urllib.request import Request, urlopen
req = Request(
"https://api.cascade.dev/import-jobs/550e8400-e29b-41d4-a716-446655440000/records",
headers={"Authorization": "Bearer sk_YOUR_SECRET_KEY"},
)
with urlopen(req) as res:
print(json.load(res)){
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"importJobId": "550e8400-e29b-41d4-a716-446655440000",
"recordTable": "customers",
"recordId": "550e8400-e29b-41d4-a716-446655440000",
"action": "created",
"row": 0,
"label": "string",
"deleted": true,
"createdAt": "2025-01-15T09:30:00Z"
}
],
"nextCursor": "string",
"counts": {
"created": 0,
"updated": 0,
"total": 0
}
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Unique identifier for the link between the import and the record."
},
"importJobId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The import job that wrote the record."
},
"recordTable": {
"type": "string",
"enum": [
"customers",
"products",
"orders",
"wishlists",
"reviews",
"loyaltyMemberships"
],
"description": "Which resource the record is."
},
"recordId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The record's ID. It is not a foreign key: a record deleted since the import keeps its place in the job's history."
},
"action": {
"type": "string",
"enum": ["created", "updated"],
"description": "Whether the import created the record or updated one that was already there."
},
"row": {
"anyOf": [
{
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
{
"type": "null"
}
],
"description": "The 1-based row in the CSV that became this record, where the import knew it."
},
"label": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "A short name for the record, or null when the record has been deleted since."
},
"deleted": {
"type": "boolean",
"description": "Whether the record has been deleted since the import wrote it."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the link was written."
}
},
"required": [
"id",
"importJobId",
"recordTable",
"recordId",
"action",
"row",
"label",
"deleted",
"createdAt"
],
"additionalProperties": false
},
"description": "The page of records, in the requested sort order."
},
"nextCursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Pass this back as `cursor` to fetch the next page. Null once the last page has been returned."
},
"counts": {
"type": "object",
"properties": {
"created": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many records the import created."
},
"updated": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many records the import updated."
},
"total": {
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991,
"description": "How many records the import wrote in total."
}
},
"required": ["created", "updated", "total"],
"additionalProperties": false,
"description": "Totals for the whole job, not just this page."
}
},
"required": ["data", "nextCursor", "counts"],
"additionalProperties": false
}