Automation
Keep working the way you do now.
Your PIM, ERP, DAM or a shared folder already has the data and the photos. Pick the first recipe that fits: they work with every Open Brand Exchange portal.
$ obx push ./export
Publishing to brands.kestrel.example (Kestrel, Kestrel Trail)
images: 27 checked, 3 uploaded (4.2 MB) in 3s
Done: rows: 40, images: 27 → new: 2 · updated: 3 · unchanged: 35# .github/workflows/publish.yml
on:
push:
paths: ["export/**"]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: openbrandexchange/obx-go/push-action@v0
with:
url: ${{ secrets.OBX_URL }}
token: ${{ secrets.OBX_TOKEN }}
folder: exportWhich recipe fits you?
| You have… | Use | Code needed |
|---|---|---|
| a CSV or zip on a web address, SFTP server or S3 bucket | Scheduled import | none |
| a folder where exports land | obx watch | none |
| a server or PC that runs nightly jobs | cron or Task Scheduler | one line |
| your exports in a Git repository | GitHub Action | one YAML file |
| n8n, Make or Zapier | HTTP steps | none (visual) |
| a developer who connects your PIM or ERP | The API directly | a little |
One format for everything
Every way of publishing reads the same thing: a products.csv with one row per size, and photos named {style}_{color}_{position}.jpg. Once you have that, every recipe below works.
brand,style_number,name:en,name:nl,item_type,season,materials,color_code,color_name:en,size,gtin
Kestrel,KO-1001,Merino Crew Sweater,Merino trui met ronde hals,sweater,AW26,main: 100% merino_wool,NVY,Navy,S,2000100101018
Kestrel,KO-1001,Merino Crew Sweater,Merino trui met ronde hals,sweater,AW26,main: 100% merino_wool,NVY,Navy,M,2000100101025images/
KO-1001_NVY_1.jpg ← style KO-1001, colour NVY, position 1 (front)
KO-1001_NVY_2.jpg
KO-1001_NVY_3.jpgWhat you need
In your admin, go to Automatic upload → Access keys and create a new key. You then have two values: the address of your portal (OBX_URL) and the key itself (OBX_TOKEN). The key is shown only once.
export OBX_URL=https://brands.kestrel.example
export OBX_TOKEN=obxi_… # admin → Automatic upload → Access keys (shown once)Give every system its own key. Then you can revoke one without breaking the others.
1. Scheduled import (no code)
In your admin: Automatic upload → Scheduled imports → Add scheduled import. Choose the source and how often it runs.
- Web address (HTTPS): the address of a products.csv, or of a zip with products.csv and photos. Login details are stored encrypted.
- SFTP: server, user, password or key, and the folder with the export.
- S3 bucket: AWS S3, Cloudflare R2, MinIO or Backblaze: endpoint, bucket, folder and a read-only key.
Choose "Only add and update" or "Also take missing articles offline". The second also takes offline what disappeared from the file, but stops when more than half of a brand would disappear, so a broken export never empties your catalog. "Test connection" shows which files it would read.
The obx tool
obx is a single program for Windows, macOS and Linux. It reads OBX_URL and OBX_TOKEN from the environment, uploads only new or changed photos, makes the web versions when needed and waits until your portal has processed everything. Run it as often as you like: unchanged articles cost nothing.
obx info # check the token, see limits and brands
obx push --csv products.csv --images ./images # publish a CSV and a folder (or zip) of images
obx push --package export.zip # the same, as one zip
obx push --csv products.csv --images ./images --mirror # also take offline what is no longer in the file
obx push --csv products.csv --images ./images --dry-run # only check, change nothing
obx watch ./outbox # keep publishing whatever lands in a folder
obx status # recent jobs and their errorsExit code 0 = all good, 2 = done but some rows had errors (they are listed), 1 = failed.
2. A folder that publishes itself: obx watch
Let your PIM, ERP or a colleague save exports into a folder. obx watch publishes whenever something changes and skips photos that stayed the same. Run it as a service, or with Docker:
obx watch /data/outboxdocker run -d --restart unless-stopped \
-e OBX_URL -e OBX_TOKEN \
-v /data/outbox:/outbox \
ghcr.io/openbrandexchange/obx watch /outbox3. cron or Windows Task Scheduler
# crontab -e: every night at 02:30
30 2 * * * OBX_URL=https://brands.kestrel.example OBX_TOKEN=obxi_… /usr/local/bin/obx push --csv /exports/products.csv --images /exports/images >> /var/log/obx.log 2>&1$action = New-ScheduledTaskAction -Execute "C:\Tools\obx.exe" -Argument "push --csv D:\Export\products.csv --images D:\Export\images"
$trigger = New-ScheduledTaskTrigger -Daily -At 2:30am
[Environment]::SetEnvironmentVariable("OBX_URL", "https://brands.kestrel.example", "Machine")
[Environment]::SetEnvironmentVariable("OBX_TOKEN", "obxi_…", "Machine")
Register-ScheduledTask -TaskName "Publish to OBX" -Action $action -Trigger $trigger -User "SYSTEM"4. GitHub Action
Store OBX_URL and OBX_TOKEN as repository secrets. The same works in GitLab CI, Bitbucket or Azure DevOps: install obx and run obx push.
# .github/workflows/publish.yml (secrets: OBX_URL, OBX_TOKEN)
name: Publish product content
on:
push:
branches: [main]
paths: ["export/**"]
schedule:
- cron: "30 2 * * *"
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install obx
run: |
curl -fsSL https://github.com/openbrandexchange/obx-go/releases/latest/download/obx_linux_amd64.tar.gz | tar -xz obx
sudo mv obx /usr/local/bin/
- name: Publish
env:
OBX_URL: ${{ secrets.OBX_URL }}
OBX_TOKEN: ${{ secrets.OBX_TOKEN }}
run: obx push --csv export/products.csv --assets-csv export/assets.csv --images export/images5. n8n, Make or Zapier
Use a plain HTTP Request step with the header Authorization: Bearer followed by your key. Every step is safe to repeat: sending the same data twice changes nothing.
PUT {OBX_URL}/obx/v1/ingest/products/KO-1001
Authorization: Bearer obxi_…
Content-Type: application/json
{"style_number": "KO-1001",
"name": {"en": "Merino Crew Sweater", "nl": "Merino trui met ronde hals"},
"item_type": "sweater", "season": "AW26",
"materials": [{"part": "main", "fibers": [{"fiber": "merino_wool", "percentage": 100}]}]}PUT {OBX_URL}/obx/v1/ingest/variants/2000100101025
Authorization: Bearer obxi_…
Content-Type: application/json
{"product_external_id": "KO-1001", "gtin": "2000100101025",
"color": {"code": "NVY", "name": {"en": "Navy"}}, "size": "M"}PUT {OBX_URL}/obx/v1/ingest/assets/KO-1001_NVY_1
Authorization: Bearer obxi_…
Content-Type: application/json
{"product_external_id": "KO-1001", "color_code": "NVY",
"role": "packshot", "view": "front", "position": 1}
PUT {OBX_URL}/obx/v1/ingest/assets/KO-1001_NVY_1/files/original
Authorization: Bearer obxi_…
Content-Type: image/jpeg
<the image file as binary body>The Portal for Cloudflare does not make web versions of photos itself. Publish with obx or with "Imports" in your admin there: they make them for you. Photos that arrive another way, such as from a scheduled import, are shown in their original size until then.
PATCH {OBX_URL}/obx/v1/ingest/products/KO-1001
Authorization: Bearer obxi_…
Content-Type: application/merge-patch+json
{"status": "unpublished"}Want a signal when something happens? In your admin, add a notification (webhook) for a finished import or a new access request, and let it start an n8n, Make or Zapier trigger.
6. The API directly (for developers)
An integration remembers what it sent last time and sends only the changes, in batches. Or send the CSV as it is and let the portal read it.
URL="$OBX_URL/obx/v1/ingest"; AUTH="Authorization: Bearer $OBX_TOKEN"
# 1. Upload only the images the node does not have yet (addressed by sha256).
for f in images/*.jpg; do
sha=$(shasum -a 256 "$f" | cut -d' ' -f1)
curl -sf -I -H "$AUTH" "$URL/blobs/$sha" >/dev/null || \
curl -sf -X PUT -H "$AUTH" -H 'Content-Type: image/jpeg' --data-binary @"$f" "$URL/blobs/$sha"
done
# 2. One batch of up to 1000 operations (NDJSON), processed in order.
cat > batch.ndjson <<'EOF'
{"op":"upsert","kind":"product","external_id":"KO-1001","data":{"style_number":"KO-1001","name":{"en":"Merino Crew Sweater"}}}
{"op":"upsert","kind":"variant","external_id":"2000100101025","data":{"product_external_id":"KO-1001","gtin":"2000100101025","color":{"code":"NVY"},"size":"M"}}
{"op":"unpublish","kind":"product","external_id":"KO-0999"}
EOF
curl -s -X POST -H "$AUTH" -H 'Content-Type: application/x-ndjson' \
-H "Idempotency-Key: nightly-$(date +%F)" --data-binary @batch.ndjson "$URL/batch"curl -s -X POST -H "Authorization: Bearer $OBX_TOKEN" \
-F [email protected] -F [email protected] \
-F 'options={"mode":"upsert"};type=application/json' \
"$OBX_URL/obx/v1/ingest/imports" # returns a job; poll $OBX_URL/obx/v1/ingest/jobs/<id>Every detail is in the specification:Ingest API ·automation recipes ·Go SDK ·Developers