1. Imports
  2. Create Import

Imports

Create Import

POST
`/v1/imports`

Creates a bulk import operation for one of the supported resources: contact, group, item, asset, or location. The data can be supplied either as a JSON array of objects or as a base64-encoded CSV/XLSX/JSON file. Only upload_mode: "sync" is currently supported.

Actions per Resource

The action you may use depends on the resource:

Resource merge overwrite delete
contact yes yes yes
group yes yes yes
item yes no no
asset yes no no
location yes no no
  • merge: Updates and/or creates the rows in the upload.
  • overwrite: Creates and/or updates the rows in the upload, and deletes/detaches all other existing rows of that resource. See the location_id and overwrite section below for how the deletion scope works. Only valid for contact and group.
  • delete: Deletes the rows in the upload. A valid id is required for each row. Only valid for contact and group.

Using an action that a resource does not support returns a 400 error, for example: 'delete' cannot be used with Imports API for 'items'.

location_id and overwrite

This section eliminates a common misunderstanding. Read it fully before using overwrite with a location_id.

1. location_id is only permitted with action: "overwrite". Providing location_id with any other action returns a 400 error: 'location_id' can only be used with Imports API in 'overwrite' mode. Because overwrite is only valid for contact and group, location_id is only meaningful for contact and group imports.

2. location_id exists for ONE purpose: to scope the overwrite deletion to a single location. It answers the question "when I overwrite, should the existing resources be removed across my entire organization, or only at this one location?"

  • Without location_id, an overwrite deletes every existing contact/group of that resource that is NOT present in the upload, organization-wide.
  • With location_id, the overwrite only removes the resource's association with THAT location for rows not present in the upload. For contacts, a contact is deleted entirely only if it is left with no locations at all afterward.

3. location_id does NOT associate the imported rows to that location. Providing location_id does not link, tag, or place the newly imported or updated rows at that location. It is purely the deletion scope. If you want an imported row to belong to a location, you MUST specify that location explicitly in the row's own data (its CSV column or JSON field). To repeat, because this is the exact ambiguity to avoid: location_id controls what gets deleted, never what the imported rows get associated to.

4. How a row explicitly associates itself to a location (per resource):

Resource Field in the row's data Format
contact locations Comma-separated location names or ids.
group location A single location name. For location-type groups, the children are locations, each formatted name:::email:::id_number and joined by commas.
item levels[].location One or more location names joined by ::: (aligned positionally with the other levels[].* columns).
asset location_name A single location name.

Example A - Overwrite contacts at a location AND associate them to it

Here each contact row sets the locations field, so the uploaded contacts are associated to loc_wework01 because the data says so. The location_id scopes the overwrite so that only contacts currently at loc_wework01 and not present in this upload are removed from that location.

js
        const response = await fetch("https://api.packagex.io/v1/imports", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "resource": "contact",
    "upload_mode": "sync",
    "action": "overwrite",
    "location_id": "loc_wework01",
    "data": [
      { "name": "Leia Organa", "email": "leia@packagex.xyz", "locations": "WeWork 01" },
      { "name": "Han Solo", "email": "han@packagex.xyz", "locations": "WeWork 01" }
    ]
  })
}).then((res) => res.json());

      

Outcome: Leia and Han are created/updated and associated to WeWork 01 (because their rows list it under locations). Any other contact previously associated to WeWork 01 that is not in this upload is removed from that location - and deleted entirely only if it has no remaining locations.

Example B - The trap: overwrite with location_id but no locations in the data

This is the same action: "overwrite" and location_id, but the rows do NOT set locations. This is the mistake to avoid.

js
        const response = await fetch("https://api.packagex.io/v1/imports", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "resource": "contact",
    "upload_mode": "sync",
    "action": "overwrite",
    "location_id": "loc_wework01",
    "data": [
      { "name": "Leia Organa", "email": "leia@packagex.xyz" },
      { "name": "Han Solo", "email": "han@packagex.xyz" }
    ]
  })
}).then((res) => res.json());

      

Outcome: existing contacts at WeWork 01 that are not in this upload are still removed from that location (the deletion scope applies). But Leia and Han are created/updated WITHOUT being associated to WeWork 01, because their rows never specified a locations value. If you expected them to end up at WeWork 01, this is wrong - location_id alone will not do that. Set locations on each row (as in Example A) instead.

The base64 CSV equivalent works the same way: the CSV must contain a locations column with the location for each row. Because base64 hides the row contents, the readable JSON arrays above are preferred when a location association matters.

Examples

Import Contacts (base64 CSV)

js
        const response = await fetch("https://api.packagex.io/v1/imports", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "data": "data:text/csv;base64,bmFtZSxlbWFpbCxub3Rlcyxsb2NhdGlvbnMKU2VlIFRocmVlcGlvLHNlZS50aHJlZXBpb3FjYmJ3ZHN5ZnNiZnprMmMzaHpod2hAcGFja2FnZXgueHl6LGRyb2lkLApLeWxvIFJlbixiZW4uc29sb3R6Y3lpcGU0ZTMxeXhwYWRiMnNqaXpAcGFja2FnZXgueHl6LEphY2VuPywKS3lsbyBSZW4sLEJlbj8sClJleSBTa3l3YWxrZXIscmV5LnNreXdhbGtlcmhyZXF1cmlwOXNodGtremlzc21tYWFAcGFja2FnZXgueHl6LCwiX19TZWFyY2ggQ29udGFjdHMgVGVzdCxBQSBXcml0ZSBUZXN0IgpIYW4gU29sbywscmVhbD8sCkhhbiBTb2xvLCx1bnJlbGE/LApMdWtlIFNreXdhbGtlcix3cm9uZywsCg==",
    "upload_mode": "sync",
    "action": "merge",
    "duplicate_handling": "use_first",
    "resource": "contact"
  })
}).then((res) => res.json());

      

Import Contacts (JSON data)

js
        const response = await fetch("https://api.packagex.io/v1/imports", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "resource": "contact",
    "upload_mode": "sync",
    "action": "merge",
    "duplicate_handling": "ignore_all",
    "data": [
      { "name": "Frodo Baggins", "email": "frodo@packagex.xyz", "locations": "WeWork 01,WeWork 02" },
      { "name": "Samwise Gamgee", "email": "sam@packagex.xyz" }
    ]
  })
}).then((res) => res.json());

      

Import Groups (JSON data)

The resource column on each group row is required and is either contact or location. For location-type groups the children are locations.

js
        const response = await fetch("https://api.packagex.io/v1/imports", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "resource": "group",
    "upload_mode": "sync",
    "action": "merge",
    "data": [
      { "name": "Fellowship", "resource": "contact", "children": "Frodo Baggins:::frodo@packagex.xyz,Samwise Gamgee:::sam@packagex.xyz" },
      { "name": "Shire Offices", "resource": "location", "location": "WeWork 01", "children": "WeWork 01:::,WeWork 02:::" }
    ]
  })
}).then((res) => res.json());

      

Import Items (JSON data)

Stock levels use the positional levels[].* columns; multiple levels are joined by :::. Items only support the merge action.

js
        const response = await fetch("https://api.packagex.io/v1/imports", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "resource": "item",
    "upload_mode": "sync",
    "action": "merge",
    "data": [
      {
        "name": "Widget",
        "sku": "W-001",
        "levels[].location": "WeWork 01:::WeWork 02",
        "levels[].verified_qty": "10:::5"
      }
    ]
  })
}).then((res) => res.json());

      

Import Assets (JSON data)

An asset associates to a single location via location_name. Assets only support the merge action.

js
        const response = await fetch("https://api.packagex.io/v1/imports", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "resource": "asset",
    "upload_mode": "sync",
    "action": "merge",
    "data": [
      { "name": "Laptop 1", "barcode": "ASSET-001", "location_name": "WeWork 01" }
    ]
  })
}).then((res) => res.json());

      

Import Locations (JSON data)

Locations only support the merge action. A row targets an existing location when it includes an id or a matching external_id; otherwise a new location is created (dedup priority: id > external_id > name).

js
        const response = await fetch("https://api.packagex.io/v1/imports", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "resource": "location",
    "upload_mode": "sync",
    "action": "merge",
    "data": [
      { "name": "Main Warehouse", "code": "MWH", "address": "123 Main St, New York, NY 10001", "service_levels": "same_day,next_day" },
      { "name": "East Hub", "external_id": "hub-east-001", "address": "456 Park Ave, Boston, MA 02101" }
    ]
  })
}).then((res) => res.json());

      

Delete Contacts

delete requires a valid id per row and is only valid for contact and group.

js
        const response = await fetch("https://api.packagex.io/v1/imports", {
  method: "POST",
  headers: {
    "PX-API-KEY": process.env.PX_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "resource": "contact",
    "upload_mode": "sync",
    "action": "delete",
    "data": [
      { "id": "ctct_123" },
      { "id": "ctct_456" }
    ]
  })
}).then((res) => res.json());

      

Request Model

This request model represents the payload structure required to create an import operation.


Request Body

upload_mode sync|async
Determines whether the import runs synchronously (sync) with data or asynchronously (async) without immediate data. Only sync is currently supported - async returns a 400 error.

  • If sync, the data field is required.
  • If async, the data field must be null.

data array<object> | base64 | null
The data to be imported, which can be:

  • An array of objects, where each object is one row.
  • A base64-encoded file (CSV, XLSX, or JSON) that resolves to an array of objects.
  • null when upload_mode is async.
Show Details
Contact columns
name string (1-255)
Name of the contact (required).
alternate_names string (1-1279)
Comma separated alternate names of the contact. Max 5
email string (max 127)
Primary email of the contact.
alternate_emails_other string (max 127)
Other alternate email.
alternate_emails_personal string (max 127)
Personal alternate email.
alternate_emails_work string (max 127)
Work alternate email.
alternate_emails_social string (max 127)
Social alternate email.
alternate_emails_education string (max 127)
Education alternate email.
phone string (max 31)
Primary phone number.
alternate_phones_home string (max 31)
Home alternate phone.
alternate_phones_mobile string (max 31)
Mobile alternate phone.
alternate_phones_work string (max 31)
Work alternate phone.
alternate_phones_other string (max 31)
Other alternate phone.
alternate_phones_education string (max 31)
Education alternate phone.
address string (max 255)
Primary address of the contact.
alternate_addresses_secondary string (max 255)
Secondary alternate address.
alternate_addresses_home string (max 255)
Home alternate address.
alternate_addresses_work string (max 255)
Work alternate address.
alternate_addresses_education string (max 255)
Education alternate address.
alternate_addresses_other string (max 255)
Other alternate address.
locations string (max 2047)
Comma separated names or ids of locations the contact belongs to.
groups string (max 2047)
Comma separated names of groups.
id_number string
Unique identifier.
is_primary boolean
Indicates if this is the primary contact.
notes string (max 255)
Additional notes.
Group columns
name string (1-255)
Name of the group (required).
resource contact|location
Resource type of the group (required).
location string (max 63)
Name of the group's location.
children string (max 2047)
Comma separated members. For a contact group each child is name:::email:::id_number; for a location group each child is a location.
Item columns
name string
Name of the item.
sku string
Stock keeping unit.
description string
Item description.
gtin string
Global trade item number.
upc string
Universal product code.
attributes string
Item attributes.
vendor string
Vendor name.
origin_country string
Country of origin.
image_urls string
Comma separated image URLs or base64 images.
is_asset boolean
Whether the item is an asset.
harmonized_code string
Harmonized tariff code.
color string
Item color.
size string
Item size.
length / width / height / weight string
Item dimensions and weight.
packaged_length / packaged_width / packaged_height / packaged_weight string
Packaged dimensions and weight.
value number
Item value.
metadata string
Additional metadata.
levels[].location string
Location name(s) for stock levels, joined by :::.
levels[].layout string
Layout name(s) for stock levels, joined by :::.
levels[].verified_qty string
Available quantity per level, joined by :::.
levels[].defective_qty string
Defective quantity per level, joined by :::.
Asset columns
name string
Name of the asset.
barcode string
Asset barcode.
assignee string
Contact id the asset is assigned to.
category / sub_category string
Asset category and sub-category.
gtin / sku / serial_number string
Identifiers.
description string
Asset description.
vendor string
Vendor name.
origin_country string
Country of origin.
status string
Asset status.
harmonized_code string
Harmonized tariff code.
color / size string
Asset color and size.
length / width / height / weight string
Dimensions and weight.
packaged_length / packaged_width / packaged_height / packaged_weight string
Packaged dimensions and weight.
value / initial_value number
Asset value.
currency string
Currency code.
metadata string
Additional metadata.
layout_name string
Layout name.
location_name string
Name of the location the asset belongs to.
archived boolean
Whether the asset is archived.
purchased_at / warranty_expires_at / next_maintenance_at / last_maintenance_at date
Asset dates.
maintenance_status string
Maintenance status.
Location columns
name string (max 127)
Location display name. Dedup key when neither id nor external_id is given.
id string (loc_ prefix)
Targets an existing location and bypasses deduplication.
external_id string (max 127)
External system id. Used to match an existing location; a new one is created if not found.
alternate_external_id string (max 127)
Alternate external system id.
code string (max 127)
Short location code.
email string (max 255)
Location email address.
phone string (max 31)
Location phone number.
address string (max 511)
Full address string. Also accepts the address.formatted_address alias column.
address_line2 string (max 255)
Second address line (suite, floor, etc.).
service_levels string (max 511)
Comma-separated service levels, parsed into an array e.g. "same_day,next_day". Each value must be a valid service level.
provider_instructions string (max 511)
Delivery provider instructions.
metadata string
JSON string of key-value metadata pairs.

location_id string | null (optional)
Scopes an overwrite deletion to a single location. Only permitted with action: "overwrite" (otherwise returns a 400). It does NOT associate imported rows to the location - see the location_id and overwrite section above.

action merge|overwrite|delete
The action to perform. Valid values depend on resource (see the Actions per Resource table above):

  • merge: Updates and/or creates the rows in the upload.
  • overwrite: Creates/updates the rows in the upload and deletes/detaches all other rows (scoped by location_id if provided). Only for contact and group.
  • delete: Deletes the rows in the upload; a valid id is required per row. Only for contact and group.

Note: item, asset, and location support merge only.

duplicate_handling ignore_all|use_first (optional, default: ignore_all)
Defines how duplicate records are handled. With use_first, the first instance of a duplicate is processed and the rest are ignored. With ignore_all, all duplicates are ignored.

save_upload boolean (default: true)
Whether the uploaded payload is stored so it can be downloaded later via the retrieve endpoint. If false, requesting the upload file on retrieve returns a 400.

resource contact|group|item|asset|location
The type of resource being imported.

name string (optional, 3-127 characters)
A human-readable name for the import. Values shorter than 3 or longer than 127 characters return a 400.

column_mapping object (optional)
A mapping of resource field names to the custom column headers used in your uploaded file. This lets you import files whose headers do not match the expected field names. Keys are the resource field names and values are the corresponding header strings. Each non-null header value must be unique - duplicate values return a 400: Headers must be unique for each column. Set a value to null to skip mapping that column.