Skip to content

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.

Terminal window
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"
}
]
}
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.

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" }
]
}

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.

  • GS1 and bulk import must both be on the plan — otherwise the whole call returns 402 before anything is written.
  • Multiple link types in a linkset, and any ai attributes, are part of Resolver. Without it those rows come back failed with a feature_locked message 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.

GS1 codes contain /, so they’re addressed through their own path:

GET|PATCH|DELETE /v1/links/gs1/{code} — scopes links:read / links:write

Terminal window
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.