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. An import writes rows and nothing else. It skips what an entity's own routes do on a write. A company created by an import, for example, isn't set up for its buyers to sign in, so create companies and contacts through their own routes.

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. full-sync deletes every row of the entity that is not in the file. See Choose which system owns each entity before you use it.
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}.

Translated values

Translated values are written under the tenant's write key, never under the spelling the file happens to use: a BMEcat locale of de_DE lands under de or de-DE, depending on the tenant's locale policy.

CSV, JSON, XLSX and XML imports can write translated text too, by mapping a column to labels.<locale> or locale_specific.<locale>.<code>. A row whose locale the tenant cannot resolve is rejected with the reason invalid_locale rather than stored under a key no storefront reads. Set dry_run on a run to see the outcome and the warnings without keeping anything.

When a file carries only some languages, the profile decides what happens to the others: merge (default) overwrites only the values in the file, replace_locales replaces only the languages the file contains, replace overwrites the whole document. With the first two, a German file and an English file never erase each other. See Importing several languages.

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?