Full chapter replace (upsert). The labelling chapter belongs to this specification only (never a library chapter).
Optional optimistic concurrency: the ETag returned by the chapter GET. When provided, the write is rejected with 412 if the specification changed since the chapter was read (anything on it: another chapter, a contact, a share), or, for a library chapter, if the chapter changed. Guards against concurrent writers within the same owner company (cross-company overwrite is already impossible — the chapter is the ownership boundary).
Section pack_ingredient_list — INPUTS ONLY, keyed by production chapter. The list text is generated from each production chapter's recipe and from the declaration rows below, and is read-only (IngredientDeclarationRead generatedText). The rows themselves come from the recipe: they are addressed, never created or removed, except the groups.
An entry addresses a production chapter linked to the specification (on a create: referenced by this same create). A productionChapterId that is not linked is a 422 naming the entry; the same productionChapterId twice is a 422 naming the second occurrence. Entries are not created or removed here: a linked chapter the array does not carry keeps what it holds.
Within an entry:
- overrideText: null or absent removes the override (the generated text is printed).
- displayOnPack: null or absent is true.
- declareAllergens: null or absent keeps the current value.
- characteristicIngredients: replaces the list in full; null or absent keeps it, [] clears it. An item that is not an ingredient of the production chapter's recipe is a 422 naming the section.
- rows: sets the declaration of the rows it names (by rowId, as read on IngredientDeclarationRead); a row it does not name keeps its declaration. Within a row, a null or absent field keeps its value. Refused with a 422 naming the item: a rowId that is not on the list; a declarationType not allowed for the row's ingredient type; any change to a row whose declarationType is NotApplicable; a quid on a row declared DoNotDeclare or NotApplicable.
- groups: replaces the groups in full; null or absent keeps them, [] removes them all. A group carrying a groupId already on the list is kept (its name, declarationType and members are updated); a group without groupId is created; a group on the list the array does not carry is removed, its members go back to their parent. Refused with a 422 naming the item: a memberRowId that is not on the list; members with different parents; a member declared DoNotDeclare or NotApplicable; a group declarationType other than DeclareAsSingle or DeclareAsComposite.
Rows are applied before groups; the list text is regenerated once, after the entry is applied.
Merged (assortment) mode is out of scope: on a specification in merged mode the section is refused with a 422 naming the section.
Section pack_nutrition — INPUTS ONLY, keyed by production chapter. The per-100 g values, their units and the default serving come from the production chapter's nutrition table and are read-only here: they are written on the production chapter (FoodProductionChapterWrite.nutrition). Per-serving values are computed for a serving whose automaticCalculation is true, and entered by the caller for a serving whose automaticCalculation is false.
An entry addresses a production chapter linked to the specification (on a create: referenced by this same create). A productionChapterId that is not linked is a 422 naming the entry; the same productionChapterId twice is a 422 naming the second occurrence. Entries are not created or removed here: a linked chapter the array does not carry keeps what it holds.
Within an entry:
- servings: replaces the ADDITIONAL servings in full (the default serving is read-only); null or absent keeps them, [] removes them all. A servingId already on the table updates that serving; a servingId not on the table adds a serving with that id; a serving on the table the array does not carry is removed with its per-serving values. The default serving's id is a 422 naming the item; the same servingId twice is a 422 naming the second occurrence.
- nutrients: sets what it names; a nutrient it does not name keeps what it holds. A nutrientId absent from the production chapter's nutrition table is a 422 naming the item. displayToConsumer null or absent keeps the flag. perServing carries the manual values of a serving whose automaticCalculation is false (after the servings above are applied); a servingId of a serving whose automaticCalculation is true, or that is not on the table, is a 422 naming the item. Within a perServing item, null or absent keeps the value.
- nutritionalAnalysisFileIds: replaces the analysis files in full; null or absent keeps them, [] clears them. A file that does not exist or is not a nutritional-analysis file is a 422 naming the section.
Merged (assortment) mode is out of scope: on a specification in merged mode the section is refused with a 422 naming the section.
The manual claims the chapter carries, replaced in full.
REPLACE ALGORITHM — the section holds claims of both origins, so the replace is scoped to the manual ones:
- a manual claim absent from the array is removed;
- a manual claim present in the array is kept as it stands (its server-side detail is not reset);
- an id not yet on the chapter is added as a manual claim, in the list its claimType names;
- an AUTOMATIC claim is never touched — not removed, not converted, not counted as a duplicate of a manual one.
Sending the id of a claim that is currently AUTOMATIC does not adopt it: it stays automatic and no second entry appears.
The same id twice under the SAME claimType is collapsed silently; the same id under two different types is a contradiction, refused with a 422 naming the second occurrence's claimType.
null or absent means the section is NOT supplied and keeps what the chapter already holds (template defaults on a create) — it does NOT clear the manual claims. Send [] to clear them.
ERROR ATTRIBUTION. Refusals the payload alone can produce — a missing claimId or claimType, the duplicate contradiction above — name the exact entry (labellingChapter.claims[1].claimType). Everything about the claim ITSELF is checked by the platform when the link is written: the claim exists, is of the specification's product type, is the active version, and — for a library claim — belongs to the specification owner. Those refusals are 422s naming the SECTION (labellingChapter.claims), and the message identifies the offending id.
A marketing or nutritional claim id that exists but is not visible to your company answers 403: treat 403 on this section as "one of these claim ids is not yours".
Section pack_product_info — WRITE side. Values copied from the trade item (product name, GTIN, the trade item's own net content, variable weight, drained net weight) are NOT writable: they derive from tradeItemId server-side and appear on the read side; here only their on-pack flags are set. Extra net contents are written in full. Absent fields keep the template defaults.
Section nutriscore — INPUTS ONLY, keyed by production chapter. An entry addresses a production chapter linked to the specification (on a create: referenced by this same create, productionChapter.chapterId); a productionChapterId that is not linked is a 422 naming the entry. Entries are not created or removed here: a linked chapter not carried in the array keeps what it holds. null or absent = section not supplied.
Within an entry only productionChapterId is required. A field that is null or absent keeps what the entry holds (empty on a create): the platform's own validation flags an entry without a product type or a percentage as incomplete; it does not refuse it.
Merged (assortment) mode is out of scope: this write addresses the per-chapter entries. A chapter later put in merged mode keeps one merged entry under another key and is not writable here.
Section consumer_instruction — four blocks, each supplied independently (see the block semantics above).
Section sorting_instruction — target streams with country-specific components, plus the recycling logos.
displaySortingInstructions is not only a display flag: turning it OFF CLEARS the streams (the platform drops them when the section is not shown on pack). A payload carrying false together with a non-empty streams array is therefore a contradiction, refused with a 422 naming displaySortingInstructions - the streams would otherwise be accepted and then removed.
streams and the countries inside a stream are each replaced in full when supplied; absent or null keeps what the chapter holds (template defaults on a create). A stream is keyed by targetStreamId and a country row by countryId - the same key twice is a 422 naming the second occurrence.
Section gmo_risk_analysis — the inputs of the rows the platform generated from the recipe.
Each entry addresses one generated row by materialId AND ingredientId (see the note above: an absent ingredientId is part of the key, not an omitted field). A pair that matches no generated row is a 422 naming the entry, and the same pair twice is a 422 naming the second occurrence. A row the array does not carry keeps what it holds.
null or absent = section not supplied.
Section pack_business_name_and_address: the consumer-service contact and an additional text printed on pack, and whether the health marks and packer codes of the production sites are shown. The codes themselves come from the production chapters' sites and are read on BusinessNameAndAddressRead.
Custom field VALUES, one entry per custom component of the chapter. The template places retailer-defined custom components in the chapter; each component is a group of fields with its own published JSON Schema, listed by GET /v2/public/templates/{templateId}/chapters/ {chapterType}/custom-fields-schema. A published component never changes, and the same componentId is valid in every template that uses it.
WRITE RULES
- An entry replaces the values of its component: a field the values object does not carry is cleared. A component the array does not carry keeps what it holds. null or absent = not supplied.
- An entry is addressed by componentId. sectionCode is needed only when the component appears in more than one section of the chapter; without it, that is a 422 naming the missing field, customFields[i].sectionCode.
- A componentId (or componentId + sectionCode) that is not in the chapter, or the same one twice: 422 naming the entry.
- To change one field, read the entry, change it and send the whole values object back, with If-Match: the ETag of the chapter read. Without it, an edit made in between by the counterpart company is overwritten.
- values is validated against the component's JSON Schema (types, formats, bounds, reference-list items, files owned by your company). A refusal is a 422 naming the field,
customFields[i].values.<field>. - Mandatory fields (CustomFieldsSchema mandatoryFields) are not checked on write; they are checked when the section is validated.
- Who may write follows the section that holds the component: in a custom section either party may write; in a standard section the section's own rule applies (e.g. retailer or supplier sections of the general chapter: owner company only). A read returns one entry per custom component of the chapter, with sectionCode always set.
- Productionhttps://apis.traceone.com/spec/v2/food/specifications/{specificationId}/labelling-chapter
curl -i -X PUT \
'https://apis.traceone.com/spec/v2/food/specifications/{specificationId}/labelling-chapter' \
-H 'Authorization: Bearer <YOUR_TOKEN_HERE>' \
-H 'Content-Type: application/json' \
-H 'If-Match: string' \
-d '{
"ingredientDeclaration": {
"entries": [
{
"productionChapterId": "cfcca4aa-1aec-4368-ab36-481357651a83",
"overrideText": "string",
"displayOnPack": true,
"declareAllergens": true,
"characteristicIngredients": [
{
"materialId": "30cdab3c-3e2b-44f4-8415-c6dd88ed4261",
"ingredientId": "a7a9b407-8421-41ba-937c-6c43b14e4701"
}
],
"rows": [
{
"rowId": "c4e73cac-8b0c-4e55-94d1-531330067e81",
"declarationType": "DoNotDeclare",
"declareAs": "string",
"quid": 0
}
],
"groups": [
{
"groupId": "eb54e96e-21b8-4f54-9cd4-80fccbd06f55",
"name": "string",
"declarationType": "DoNotDeclare",
"memberRowIds": [
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
]
}
]
},
"nutritionalInformation": {
"entries": [
{
"productionChapterId": "cfcca4aa-1aec-4368-ab36-481357651a83",
"servings": [
{
"servingId": "bc112818-bc5f-43a6-93e6-8e617a7c8f81",
"name": "string",
"value": 0,
"massUnitId": "63feeca3-19ff-4722-8955-6713ff5719ce",
"volumeUnitId": "9c8961f2-1e43-4bc8-95fa-bac5491cc67e",
"servingTypeCode": "string",
"automaticCalculation": true
}
],
"nutrients": [
{
"nutrientId": "ce8c0866-b8a2-4223-9b70-6737aeeac3f5",
"displayToConsumer": true,
"perServing": [
{}
]
}
],
"nutritionalAnalysisFileIds": [
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
]
},
"claims": [
{
"claimId": "88057929-7728-4a26-8396-f965dcc8b947",
"claimType": "Marketing"
}
],
"productInformation": {
"productName": {
"displayOnPack": true
},
"commercialName": {
"value": "string",
"subtitle": "string",
"displayOnPack": true
},
"legalName": {
"value": "string",
"subtitle": "string",
"displayOnPack": true
},
"productDescription": {
"value": "string",
"displayOnPack": true
},
"productGtin": {
"displayOnPack": true
},
"netContents": [
{
"unitId": "b3eb5f92-604a-46b9-9881-9d84000fd7ed",
"eMark": true,
"displayOnPack": true
}
],
"additionalNetContents": [
{
"unitId": "b3eb5f92-604a-46b9-9881-9d84000fd7ed",
"value": 0,
"eMark": true,
"displayOnPack": true
}
],
"variableWeight": {
"eMark": true,
"displayOnPack": true
},
"drainedNetWeight": {
"eMark": true,
"displayOnPack": true
},
"eMark": {
"displayOnPack": true
},
"alcoholVolume": {
"percentage": 0,
"displayOnPack": true
},
"finalPageProofFileIds": [
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
},
"nutriScore": {
"entries": [
{
"productionChapterId": "cfcca4aa-1aec-4368-ab36-481357651a83",
"displayOnPack": true,
"productType": "string",
"percentageOfFruitsVegetablesAndLegumes": 0
}
]
},
"consumerInstructions": {
"storageInstructions": {
"displayOnPack": true,
"instructions": [
{
"labellingInformationId": "6bbaaf1e-1d1c-4328-8277-50a1141d8fbe",
"placeholders": [
{
"variableName": "string",
"value": "string"
}
]
}
],
"overrideText": "string"
},
"preparationInstructions": {
"displayOnPack": true,
"instructions": [
{
"labellingInformationId": "6bbaaf1e-1d1c-4328-8277-50a1141d8fbe",
"placeholders": [
{
"variableName": "string",
"value": "string"
}
]
}
],
"overrideText": "string"
},
"codesAndDates": {
"displayOnPack": true,
"consumerShelfLife": {
"dateExpression": "string",
"batchLotNumber": "string"
},
"productionIndicationIds": [
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
},
"productOrigins": {
"displayOnPack": true,
"origins": [
{
"originTypeId": "6111779f-6cec-486d-85bf-2e8ed2686050",
"landBased": {
"countryId": "bd2b151e-bbbf-43a3-8058-286e653dfac0",
"regionId": "a29f01e1-8a8d-451e-b685-f8b0b4ec4767",
"zoneId": "c3920607-5069-4ac3-ba10-00754e7a8e8b"
},
"waterBased": {
"fishingZoneIds": [
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
],
"fishingGearIds": [
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}
],
"overrideText": "string"
}
},
"sortingInstructions": {
"displaySortingInstructions": true,
"streams": [
{
"targetStreamId": "f9b5c8f0-959c-4c0c-95f6-ea88ef2d81fc",
"countries": [
{
"countryId": "bd2b151e-bbbf-43a3-8058-286e653dfac0",
"components": "string"
}
]
}
],
"otherLogos": {
"trimanLogo": true,
"greenDotLogo": true
}
},
"gmoRiskAnalysis": {
"rawMaterialRisks": [
{
"materialId": "30cdab3c-3e2b-44f4-8415-c6dd88ed4261",
"ingredientId": "a7a9b407-8421-41ba-937c-6c43b14e4701",
"riskLevelCode": "string",
"fileIds": [
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
]
},
"additionalMarketingInformation": {
"information": "string",
"displayOnPack": true
},
"businessNameAndAddress": {
"consumerServiceInformation": {
"displayOnPack": true,
"labellingInformationId": "6bbaaf1e-1d1c-4328-8277-50a1141d8fbe",
"placeholders": [
{
"variableName": "string",
"value": "string"
}
],
"value": "string"
},
"additionalInformation": {
"value": "string",
"displayOnPack": true
},
"healthMarks": {
"displayOnPack": true
},
"packerCodes": {
"displayOnPack": true
}
},
"customFields": [
{
"componentId": "616ccd92-d666-4180-8349-8d125b269fac",
"sectionCode": "string",
"values": {}
}
]
}'No content