GS1 bulk import
POST /v1/gs1/links/bulk — scope gs1:write
Create or update GS1 Digital Links in bulk — a product range, a season’s batches, a whole catalogue. Each record is keyed by its GTIN (plus any CPV/batch/serial), so re-sending the same record updates the existing code rather than creating a duplicate. Up to 500 records per request; plan row caps apply.
Unlike short links, a GS1 code isn’t invented — it’s derived from the product
identity: 01/{gtin}[/22/{cpv}][/10/{batch}][/21/{serial}]. Send the same
GTIN and batch twice and you address the same Digital Link both times.
curl -X POST https://api.neon.xengo.io/v1/gs1/links/bulk \ -H "Authorization: Bearer $XENGO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tags": ["spring-2026"], "records": [ { "gtin": "09506000134352", "batch": "LOT01", "destination": "https://acme.com/gin" }, { "gtin": "05012345678900", "linkset": [ { "linkType": "gs1:pip", "href": "https://acme.com/rum", "default": true }, { "linkType": "gs1:instructions", "href": "https://acme.com/serve" } ], "ai": { "17": "271231" }, "tags": ["spirits"] } ] }'{ "requested": 2, "created": 2, "updated": 0, "skipped": 0, "failed": 0, "results": [ { "row": 0, "gtin": "09506000134352", "code": "01/09506000134352/10/LOT01", "status": "created" }, { "row": 1, "gtin": "05012345678900", "code": "01/05012345678900", "status": "created" } ]}Request
Section titled “Request”| Field | Type | Notes |
|---|---|---|
records |
array, required | Up to 500 records |
tags |
array of strings | Applied to every record, merged with each record’s own |
upsert |
boolean | Default true; false skips codes that already exist |
Each record:
| Field | Type | Notes |
|---|---|---|
gtin |
string, required | GTIN-8/12/13/14 — the check digit is validated |
cpv |
string | AI 22 — consumer product variant |
batch |
string | AI 10 — batch/lot |
serial |
string | AI 21 — serial number |
expiry |
string | AI 17 — YYMMDD |
destination |
string | Single destination; ignored when linkset is given |
linkset |
array | One entry per GS1 link type — { linkType, href, default } |
ai |
object | Data attributes keyed by AI, e.g. { "17": "271231" } |
tags |
array of strings | Up to 20 per record, 32 characters each |
domain |
string | One of your verified domains; defaults to yours |
The code resolves to the linkset entry flagged default, else the
gs1:pip entry, else the first one supplied.
Per-row results
Section titled “Per-row results”Records are independent — a bad row is reported and the batch continues.
Every submitted record comes back in results, in order, with its row
index so you can map failures onto your source data.
status |
Meaning |
|---|---|
created |
A new Digital Link was written |
updated |
The code already existed and was re-pointed |
skipped |
The code already existed and upsert was false |
failed |
See error on that row — the rest of the batch still ran |
{ "requested": 3, "created": 1, "updated": 1, "skipped": 0, "failed": 1, "results": [ { "row": 0, "code": "01/09506000134352", "status": "created" }, { "row": 1, "code": "01/05012345678900", "status": "updated" }, { "row": 2, "status": "failed", "error": "invalid GTIN: 12345" } ]}What an update changes
Section titled “What an update changes”An update always re-points the destination. The linkset, data attributes and tags are replaced only when that record supplies them — so a destination-only re-import leaves the existing linkset and tags untouched. Send the field to replace it; omit it to keep it.
Plan gates
Section titled “Plan gates”- GS1 and bulk import must both be on the plan — otherwise the whole
call returns
402before anything is written. - Multiple link types in a
linkset, and anyaiattributes, are part of Resolver. Without it those rows come backfailedwith afeature_lockedmessage while simpler rows in the same batch still import. - Row caps are per plan (Pro 1,000, Business 10,000) on top of the 500 hard cap per call.
Reading and editing single codes
Section titled “Reading and editing single codes”GS1 codes contain /, so they’re addressed through their own path:
GET|PATCH|DELETE /v1/links/gs1/{code} — scopes links:read / links:write
curl https://api.neon.xengo.io/v1/links/gs1/01/09506000134352/10/LOT01 \ -H "Authorization: Bearer $XENGO_API_KEY"PATCH takes the same body as a normal link — destination, status,
tags. See Links.
