Import and export

Moving catalog data in volume — the upload, import, export and bulk-job routes under /v1/io, reusable profiles, and how to poll a job to completion.

Bulk data does not go through the entity routes. It goes through the io app at /v1/io, which is shared by every app: you name the vendor, app and entity, and the same four steps work for products, price entries, organizations or your own app's tables.

What can be imported or exported

Request
curl https://api.revenexx.com/v1/io/entities \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..."

A flat list of every entity the tenant's installed apps expose, sorted by vendor, app and entity. An app with no applied schema contributes nothing. This is the list an entity picker should offer — do not hard-code one.

Import: four steps

1. Mint an upload URL

Request
curl -X POST https://api.revenexx.com/v1/io/uploads \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{"extension":"csv"}'

You get back an upload_url, the headers to send verbatim on the PUT, an object_key, and expires_in seconds. The bytes go straight to object storage — never through the API.

2. PUT the file to that URL

Send the file body to upload_url with the returned headers. Nothing else.

3. Register the object as an import job

Request
curl -X POST https://api.revenexx.com/v1/io/imports \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{
        "object_key": "io/acme-eu/9f3…/source.csv",
        "vendor": "revenexx",
        "app": "products",
        "entity": "products",
        "format": "csv",
        "mode": "upsert",
        "keys": ["sku"],
        "max_rejects": 0
      }'

object_key, vendor, app and entity are required. You get 202 with a job_id.

FieldValuesMeaning
formatcsv (default), xml, json, xlsx
modeupsert (default), full-sync, appendHow existing rows are treated.
keyscolumn namesThe natural key for upsert and delta detection. For products that is ["sku"].
max_rejectsintegerRejected rows tolerated before the import fails. Omit for unlimited (reject and continue); 0 is fail-fast.
profile_iduuidUse a saved profile's mapping instead of column-for-column.

4. Poll the job

Request
curl "https://api.revenexx.com/v1/io/bulk-jobs/{job_id}" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..."

Status, row counts and progress. GET /v1/io/bulk-jobs lists the tenant's jobs. A job id belonging to another tenant is indistinguishable from one that does not exist — both answer 404, which is the intent.

Export: two steps

Request
curl -X POST https://api.revenexx.com/v1/io/exports \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{"vendor":"revenexx","app":"products","entity":"products","format":"csv"}'

vendor, app and entity are required. The response carries the object key the result will be written to. Poll the job, then mint a download URL:

Request
curl "https://api.revenexx.com/v1/io/exports/{job_id}/url" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..."

The job must have reached completed or partial. Any earlier state answers 409 and carries the current job_status — so poll first, do not retry the URL route in a loop.

Profiles — the reusable mapping

A supplier feed does not have your column names. A profile is a stored mapping for one direction, format and entity, and it is runnable on click.

Request
curl -X POST https://api.revenexx.com/v1/io/profiles \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{
        "name": "ACME supplier feed",
        "direction": "import",
        "vendor": "revenexx",
        "app": "products",
        "entity": "products",
        "format": "csv",
        "apply_mode": "upsert",
        "mapping": {
          "fields": [
            { "target": "sku",   "source": "ArticleNo" },
            { "target": "label", "source": "Description" }
          ],
          "keys": ["sku"]
        }
      }'

mapping.fields[] carry a target column, a source external name and ordered transforms; mapping.keys[] are the natural-key columns.

Run it:

Request
curl -X POST "https://api.revenexx.com/v1/io/profiles/{id}/run" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{"object_key":"io/acme-eu/9f3…/source.csv"}'

An import run requires object_key — upload first, or it answers 422 with RUN_NO_OBJECT. An export run ignores it and generates its own key. A run may also send markets, which overrides the profile's own market assignment for that run; an empty array means the imported rows stay global.

The full profile CRUD is GET/POST /v1/io/profiles and GET/PUT/DELETE /v1/io/profiles/{id}.

After a bulk catalog change

Two things do not update themselves:

Not the only path

/v1/io is the file-based path. For a recurring feed with transformation and error handling, Integration Studio drives these same routes from a workflow, and its I/O nodes read the entity picker from GET /v1/io/entities.

The carts app keeps its own small import/export surface under /v1/carts/io/profiles for cart CSVs, which is separate from this one — see Carts.

Where to go next

Was this page helpful?