Getting Started with Sales Restrictions
This guide walks you through configuring sales restrictions on Items in Hii Retail. By the end of this guide, you will understand how to apply age restrictions, block items from sale, and configure restriction displays for in-store devices.
Overview
What are Sales Restrictions?
Sales Restrictions control when and how items can be sold at the Point of Sale (POS). They enable compliance with regulations for age-restricted products like alcohol, tobacco, and certain medications.
┌─────────────────────────────────────────────────────────────────────┐
│ Sales Restriction Flow │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Customer scans item │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ POS checks for │ │
│ │ restriction code│ │
│ └────────┬────────┘ │
│ │ │
│ Has restriction? │
│ │ │ │
│ Yes No │
│ │ │ │
│ ▼ ▼ │
│ ┌────────┐ ┌────────┐ │
│ │Prompt │ │Continue│ │
│ │cashier │ │ sale │ │
│ │for age │ └────────┘ │
│ │verify │ │
│ └────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Types of Sales Restrictions
| Restriction Type | Description | Use Case |
|---|---|---|
| Age Restriction | Requires customer to be a minimum age | Alcohol, tobacco, energy drinks |
| Sale Blocked | Prevents item from being sold entirely | Recalled products, out-of-season items |
| Hostess Notification | Alerts store staff when item is scanned | High-value items, restricted products |
Common Age Restriction Codes
| Code | Minimum Age | Typical Products |
|---|---|---|
16 | 16 years | Energy drinks, certain medications |
18 | 18 years | Tobacco, alcohol (EU), lottery tickets |
21 | 21 years | Alcohol (US), cannabis (where legal) |
25 | 25 years | Some alcohol products (Nordic countries) |
How Sales Restrictions Work
Sales restrictions are applied to Items using Additional Properties. These are key-value pairs that extend the Item entity with custom data.
Key Properties
| Property ID | Data Type | Description |
|---|---|---|
Pos.SalesRestrictionCode | STRING | Age restriction code (e.g., "18") |
Pos.PreventSaleInPos | BOOL | Completely blocks item from sale |
Label.AgeRestriction | STRING | Age restriction display for ESL labels |
Prerequisites
Before you begin, ensure you have:
- Items created: At least one Item to apply restrictions to (see Items guide)
- Authentication Token: A valid JWT bearer token with the
pnp.item.createorpnp.item.updatepermission
For details on obtaining an access token, see the OAuth2 Authentication documentation.
API Base URL
All Item Input API requests should be sent to:
https://item-input.retailsvc.com/api/v2
Step 1: Create an Item with Age Restriction
Create a new item with an age restriction applied from the start.
- cURL
- Python
- Node.js
- Java
- .NET
curl -X POST 'https://item-input.retailsvc.com/api/v2/bu-g-items' \
-H 'Authorization: Bearer <your-access-token>' \
-H 'Content-Type: application/json' \
-d '{
"id": "5012345678901",
"name": "Premium Craft Beer 330ml",
"status": "ACTIVE",
"type": "STOCK",
"itemCategoryId": "beverages-alcohol",
"salesUnitOfMeasurement": "EA",
"assortmentType": "IN_ASSORTMENT",
"businessUnitGroupId": "acme-retail-group",
"additionalProperties": [
{
"id": "Pos.SalesRestrictionCode",
"name": "Sales restriction code",
"value": "18",
"dataType": "STRING",
"isMandatory": false,
"description": "Minimum age 18 required for purchase"
}
]
}'
import requests
response = requests.post(
"https://item-input.retailsvc.com/api/v2/bu-g-items",
headers={"Authorization": f"Bearer {access_token}"},
json={
"id": "5012345678901",
"name": "Premium Craft Beer 330ml",
"status": "ACTIVE",
"type": "STOCK",
"itemCategoryId": "beverages-alcohol",
"salesUnitOfMeasurement": "EA",
"assortmentType": "IN_ASSORTMENT",
"businessUnitGroupId": "acme-retail-group",
"additionalProperties": [
{
"id": "Pos.SalesRestrictionCode",
"name": "Sales restriction code",
"value": "18",
"dataType": "STRING",
"isMandatory": False,
"description": "Minimum age 18 required for purchase"
}
]
}
)
const response = await fetch('https://item-input.retailsvc.com/api/v2/bu-g-items', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: '5012345678901',
name: 'Premium Craft Beer 330ml',
status: 'ACTIVE',
type: 'STOCK',
itemCategoryId: 'beverages-alcohol',
salesUnitOfMeasurement: 'EA',
assortmentType: 'IN_ASSORTMENT',
businessUnitGroupId: 'acme-retail-group',
additionalProperties: [
{
id: 'Pos.SalesRestrictionCode',
name: 'Sales restriction code',
value: '18',
dataType: 'STRING',
isMandatory: false,
description: 'Minimum age 18 required for purchase'
}
]
})
});
var payload = """
{
"id": "5012345678901",
"name": "Premium Craft Beer 330ml",
"status": "ACTIVE",
"type": "STOCK",
"itemCategoryId": "beverages-alcohol",
"salesUnitOfMeasurement": "EA",
"assortmentType": "IN_ASSORTMENT",
"businessUnitGroupId": "acme-retail-group",
"additionalProperties": [
{
"id": "Pos.SalesRestrictionCode",
"name": "Sales restriction code",
"value": "18",
"dataType": "STRING",
"isMandatory": false,
"description": "Minimum age 18 required for purchase"
}
]
}
""";
var request = HttpRequest.newBuilder()
.uri(URI.create("https://item-input.retailsvc.com/api/v2/bu-g-items"))
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
var payload = new {
id = "5012345678901",
name = "Premium Craft Beer 330ml",
status = "ACTIVE",
type = "STOCK",
itemCategoryId = "beverages-alcohol",
salesUnitOfMeasurement = "EA",
assortmentType = "IN_ASSORTMENT",
businessUnitGroupId = "acme-retail-group",
additionalProperties = new[] {
new {
id = "Pos.SalesRestrictionCode",
name = "Sales restriction code",
value = "18",
dataType = "STRING",
isMandatory = false,
description = "Minimum age 18 required for purchase"
}
}
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync("https://item-input.retailsvc.com/api/v2/bu-g-items", content);
Response
A successful request returns 202 Accepted.
Step 2: Add Age Restriction to Existing Item
Use JSON Patch to add a sales restriction to an existing item.
- cURL
- Python
- Node.js
- Java
- .NET
curl -X PATCH 'https://item-input.retailsvc.com/api/v2/bu-g-items/5012345678901?businessUnitGroupId=acme-retail-group' \
-H 'Authorization: Bearer <your-access-token>' \
-H 'Content-Type: application/json-patch+json' \
-d '[
{
"op": "add",
"path": "/additionalProperties/-",
"value": {
"id": "Pos.SalesRestrictionCode",
"name": "Sales restriction code",
"value": "18",
"dataType": "STRING",
"isMandatory": false,
"description": "Minimum age 18 required for purchase"
}
}
]'
import requests
response = requests.patch(
"https://item-input.retailsvc.com/api/v2/bu-g-items/5012345678901",
params={"businessUnitGroupId": "acme-retail-group"},
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json-patch+json"
},
json=[
{
"op": "add",
"path": "/additionalProperties/-",
"value": {
"id": "Pos.SalesRestrictionCode",
"name": "Sales restriction code",
"value": "18",
"dataType": "STRING",
"isMandatory": False,
"description": "Minimum age 18 required for purchase"
}
}
]
)
const response = await fetch(
'https://item-input.retailsvc.com/api/v2/bu-g-items/5012345678901?businessUnitGroupId=acme-retail-group',
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json-patch+json'
},
body: JSON.stringify([
{
op: 'add',
path: '/additionalProperties/-',
value: {
id: 'Pos.SalesRestrictionCode',
name: 'Sales restriction code',
value: '18',
dataType: 'STRING',
isMandatory: false,
description: 'Minimum age 18 required for purchase'
}
}
])
}
);
var operations = """
[
{
"op": "add",
"path": "/additionalProperties/-",
"value": {
"id": "Pos.SalesRestrictionCode",
"name": "Sales restriction code",
"value": "18",
"dataType": "STRING",
"isMandatory": false,
"description": "Minimum age 18 required for purchase"
}
}
]
""";
var request = HttpRequest.newBuilder()
.uri(URI.create("https://item-input.retailsvc.com/api/v2/bu-g-items/5012345678901?businessUnitGroupId=acme-retail-group"))
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/json-patch+json")
.method("PATCH", HttpRequest.BodyPublishers.ofString(operations))
.build();
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
var operations = new[] {
new {
op = "add",
path = "/additionalProperties/-",
value = new {
id = "Pos.SalesRestrictionCode",
name = "Sales restriction code",
value = "18",
dataType = "STRING",
isMandatory = false,
description = "Minimum age 18 required for purchase"
}
}
};
var content = new StringContent(JsonSerializer.Serialize(operations), Encoding.UTF8, "application/json-patch+json");
var request = new HttpRequestMessage(HttpMethod.Patch,
"https://item-input.retailsvc.com/api/v2/bu-g-items/5012345678901?businessUnitGroupId=acme-retail-group")
{
Content = content
};
var response = await httpClient.SendAsync(request);
The path /additionalProperties/- adds a new property to the end of the additionalProperties array. If you need to replace an existing property, use the array index (e.g., /additionalProperties/0).
Step 3: Block Item from Sale
To completely prevent an item from being sold at the POS, use the Pos.PreventSaleInPos property.
- cURL
- Python
- Node.js
- Java
- .NET
curl -X PATCH 'https://item-input.retailsvc.com/api/v2/bu-g-items/RECALLED-PRODUCT-001?businessUnitGroupId=acme-retail-group' \
-H 'Authorization: Bearer <your-access-token>' \
-H 'Content-Type: application/json-patch+json' \
-d '[
{
"op": "add",
"path": "/additionalProperties/-",
"value": {
"id": "Pos.PreventSaleInPos",
"name": "Prevent sale in POS",
"value": "true",
"dataType": "BOOL",
"isMandatory": false,
"description": "Item blocked from sale due to recall"
}
}
]'
import requests
response = requests.patch(
"https://item-input.retailsvc.com/api/v2/bu-g-items/RECALLED-PRODUCT-001",
params={"businessUnitGroupId": "acme-retail-group"},
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json-patch+json"
},
json=[
{
"op": "add",
"path": "/additionalProperties/-",
"value": {
"id": "Pos.PreventSaleInPos",
"name": "Prevent sale in POS",
"value": "true",
"dataType": "BOOL",
"isMandatory": False,
"description": "Item blocked from sale due to recall"
}
}
]
)
const response = await fetch(
'https://item-input.retailsvc.com/api/v2/bu-g-items/RECALLED-PRODUCT-001?businessUnitGroupId=acme-retail-group',
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json-patch+json'
},
body: JSON.stringify([
{
op: 'add',
path: '/additionalProperties/-',
value: {
id: 'Pos.PreventSaleInPos',
name: 'Prevent sale in POS',
value: 'true',
dataType: 'BOOL',
isMandatory: false,
description: 'Item blocked from sale due to recall'
}
}
])
}
);
var operations = """
[
{
"op": "add",
"path": "/additionalProperties/-",
"value": {
"id": "Pos.PreventSaleInPos",
"name": "Prevent sale in POS",
"value": "true",
"dataType": "BOOL",
"isMandatory": false,
"description": "Item blocked from sale due to recall"
}
}
]
""";
var request = HttpRequest.newBuilder()
.uri(URI.create("https://item-input.retailsvc.com/api/v2/bu-g-items/RECALLED-PRODUCT-001?businessUnitGroupId=acme-retail-group"))
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/json-patch+json")
.method("PATCH", HttpRequest.BodyPublishers.ofString(operations))
.build();
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
var operations = new[] {
new {
op = "add",
path = "/additionalProperties/-",
value = new {
id = "Pos.PreventSaleInPos",
name = "Prevent sale in POS",
value = "true",
dataType = "BOOL",
isMandatory = false,
description = "Item blocked from sale due to recall"
}
}
};
var content = new StringContent(JsonSerializer.Serialize(operations), Encoding.UTF8, "application/json-patch+json");
var request = new HttpRequestMessage(HttpMethod.Patch,
"https://item-input.retailsvc.com/api/v2/bu-g-items/RECALLED-PRODUCT-001?businessUnitGroupId=acme-retail-group")
{
Content = content
};
var response = await httpClient.SendAsync(request);
For product recalls, you should also set the item's status to STOPPED in addition to blocking the sale. This ensures the item is flagged system-wide.
Step 4: Remove a Sales Restriction
To remove a sales restriction, update the additional property value or remove it entirely.
Update Restriction Value
- cURL
- Python
- Node.js
- Java
- .NET
curl -X PATCH 'https://item-input.retailsvc.com/api/v2/bu-g-items/RECALLED-PRODUCT-001?businessUnitGroupId=acme-retail-group' \
-H 'Authorization: Bearer <your-access-token>' \
-H 'Content-Type: application/json-patch+json' \
-d '[
{
"op": "replace",
"path": "/additionalProperties/0/value",
"value": "false"
}
]'
import requests
# First, query the item to find the index of Pos.PreventSaleInPos
# Then update at the correct index
response = requests.patch(
"https://item-input.retailsvc.com/api/v2/bu-g-items/RECALLED-PRODUCT-001",
params={"businessUnitGroupId": "acme-retail-group"},
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json-patch+json"
},
json=[
{
"op": "replace",
"path": "/additionalProperties/0/value",
"value": "false"
}
]
)
// First, query the item to find the index of Pos.PreventSaleInPos
// Then update at the correct index
const response = await fetch(
'https://item-input.retailsvc.com/api/v2/bu-g-items/RECALLED-PRODUCT-001?businessUnitGroupId=acme-retail-group',
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json-patch+json'
},
body: JSON.stringify([
{
op: 'replace',
path: '/additionalProperties/0/value',
value: 'false'
}
])
}
);
// First, query the item to find the index of Pos.PreventSaleInPos
// Then update at the correct index
var operations = """
[
{
"op": "replace",
"path": "/additionalProperties/0/value",
"value": "false"
}
]
""";
var request = HttpRequest.newBuilder()
.uri(URI.create("https://item-input.retailsvc.com/api/v2/bu-g-items/RECALLED-PRODUCT-001?businessUnitGroupId=acme-retail-group"))
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/json-patch+json")
.method("PATCH", HttpRequest.BodyPublishers.ofString(operations))
.build();
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
// First, query the item to find the index of Pos.PreventSaleInPos
// Then update at the correct index
var operations = new[] {
new {
op = "replace",
path = "/additionalProperties/0/value",
value = "false"
}
};
var content = new StringContent(JsonSerializer.Serialize(operations), Encoding.UTF8, "application/json-patch+json");
var request = new HttpRequestMessage(HttpMethod.Patch,
"https://item-input.retailsvc.com/api/v2/bu-g-items/RECALLED-PRODUCT-001?businessUnitGroupId=acme-retail-group")
{
Content = content
};
var response = await httpClient.SendAsync(request);
To update or remove a specific additional property, you first need to query the item to determine the array index of the property you want to modify.
Country-Specific Considerations
Nordic Countries (Sweden, Norway, Finland)
- Systembolaget and Vinmonopolet have stricter age verification requirements
- Some products require 25+ age verification
- State monopoly products may have special restriction codes
European Union
- Standard minimum age of 18 for alcohol and tobacco
- Energy drinks may require 16+ in some countries
- Additional labeling requirements may apply
United States
- Minimum age 21 for alcohol in all states
- Tobacco age varies by state (18-21)
- Additional state-specific regulations may apply
Complete Example
Here's a complete example creating a tobacco product with an age restriction:
- cURL
- Python
- Node.js
- Java
- .NET
curl -X POST 'https://item-input.retailsvc.com/api/v2/bu-g-items' \
-H 'Authorization: Bearer <your-access-token>' \
-H 'Content-Type: application/json' \
-d '{
"id": "7311310000123",
"name": "Premium Cigarettes 20-pack",
"status": "ACTIVE",
"type": "STOCK",
"itemCategoryId": "tobacco-cigarettes",
"salesUnitOfMeasurement": "EA",
"assortmentType": "IN_ASSORTMENT",
"businessUnitGroupId": "acme-retail-group",
"additionalProperties": [
{
"id": "Pos.SalesRestrictionCode",
"name": "Sales restriction code",
"value": "18",
"dataType": "STRING",
"isMandatory": false,
"description": "Age 18+ required for purchase"
}
],
"identifiers": [
{
"id": "7311310000123",
"value": "7311310000123",
"type": "GTIN13",
"status": "ACTIVE",
"isPrimary": true
}
]
}'
import requests
response = requests.post(
"https://item-input.retailsvc.com/api/v2/bu-g-items",
headers={"Authorization": f"Bearer {access_token}"},
json={
"id": "7311310000123",
"name": "Premium Cigarettes 20-pack",
"status": "ACTIVE",
"type": "STOCK",
"itemCategoryId": "tobacco-cigarettes",
"salesUnitOfMeasurement": "EA",
"assortmentType": "IN_ASSORTMENT",
"businessUnitGroupId": "acme-retail-group",
"additionalProperties": [
{
"id": "Pos.SalesRestrictionCode",
"name": "Sales restriction code",
"value": "18",
"dataType": "STRING",
"isMandatory": False,
"description": "Age 18+ required for purchase"
}
],
"identifiers": [
{
"id": "7311310000123",
"value": "7311310000123",
"type": "GTIN13",
"status": "ACTIVE",
"isPrimary": True
}
]
}
)
const response = await fetch('https://item-input.retailsvc.com/api/v2/bu-g-items', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: '7311310000123',
name: 'Premium Cigarettes 20-pack',
status: 'ACTIVE',
type: 'STOCK',
itemCategoryId: 'tobacco-cigarettes',
salesUnitOfMeasurement: 'EA',
assortmentType: 'IN_ASSORTMENT',
businessUnitGroupId: 'acme-retail-group',
additionalProperties: [
{
id: 'Pos.SalesRestrictionCode',
name: 'Sales restriction code',
value: '18',
dataType: 'STRING',
isMandatory: false,
description: 'Age 18+ required for purchase'
}
],
identifiers: [
{
id: '7311310000123',
value: '7311310000123',
type: 'GTIN13',
status: 'ACTIVE',
isPrimary: true
}
]
})
});
var payload = """
{
"id": "7311310000123",
"name": "Premium Cigarettes 20-pack",
"status": "ACTIVE",
"type": "STOCK",
"itemCategoryId": "tobacco-cigarettes",
"salesUnitOfMeasurement": "EA",
"assortmentType": "IN_ASSORTMENT",
"businessUnitGroupId": "acme-retail-group",
"additionalProperties": [
{
"id": "Pos.SalesRestrictionCode",
"name": "Sales restriction code",
"value": "18",
"dataType": "STRING",
"isMandatory": false,
"description": "Age 18+ required for purchase"
}
],
"identifiers": [
{
"id": "7311310000123",
"value": "7311310000123",
"type": "GTIN13",
"status": "ACTIVE",
"isPrimary": true
}
]
}
""";
var request = HttpRequest.newBuilder()
.uri(URI.create("https://item-input.retailsvc.com/api/v2/bu-g-items"))
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
var payload = new {
id = "7311310000123",
name = "Premium Cigarettes 20-pack",
status = "ACTIVE",
type = "STOCK",
itemCategoryId = "tobacco-cigarettes",
salesUnitOfMeasurement = "EA",
assortmentType = "IN_ASSORTMENT",
businessUnitGroupId = "acme-retail-group",
additionalProperties = new[] {
new {
id = "Pos.SalesRestrictionCode",
name = "Sales restriction code",
value = "18",
dataType = "STRING",
isMandatory = false,
description = "Age 18+ required for purchase"
}
},
identifiers = new[] {
new {
id = "7311310000123",
value = "7311310000123",
type = "GTIN13",
status = "ACTIVE",
isPrimary = true
}
}
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync("https://item-input.retailsvc.com/api/v2/bu-g-items", content);
Note: Electronic Shelf Labels (ESL)
If you use Electronic Shelf Labels (ESL), you can display age restrictions on shelf labels by adding the Label.AgeRestriction property alongside the Pos.SalesRestrictionCode. This provides visual indication to customers before they reach the checkout.
Add these additional properties to your age-restricted items:
{
"additionalProperties": [
{
"id": "Pos.SalesRestrictionCode",
"name": "Sales restriction code",
"value": "16",
"dataType": "STRING",
"description": "Minimum age 16 required"
},
{
"id": "Label.AgeRestriction",
"name": "Age restriction for label",
"value": "16+",
"dataType": "STRING",
"description": "Display text for ESL"
},
{
"id": "Label.IsEsl",
"name": "ESL enabled",
"value": "true",
"dataType": "BOOL"
}
]
}
For more information on ESL configuration, see the In-Store Devices documentation.
Next Steps
Now that you understand sales restrictions, you can:
- Review Item guide: Learn how to create items with all required properties (Items guide)
- Configure prices: Set up pricing for age-restricted products (Prices guide)
- Set up taxes: Configure appropriate tax rules for restricted products (Tax guide)