Reference
REST API.
Base URL https://onpack.io/api/v1. JSON in, JSON out. All
paths are scoped by :brand_slug, and access is gated by the
bearer token on your
company.
Endpoints at a glance
Scans
Every touchpoint with your packaging. Production scans only — tester activity is excluded.
List scans
| Param | Type | Description |
|---|---|---|
since | ISO 8601 | Return scans created on or after this timestamp. |
until | ISO 8601 | Return scans created on or before this timestamp. |
limit | int, ≤1000 | Default 100. |
offset | int | Default 0. |
curl "https://onpack.io/api/v1/brands/milka/scans?since=2026-04-01T00:00:00Z&limit=50" \
-H "Authorization: Bearer pkd_••••••••••••••••"
require "net/http"; require "json"
uri = URI("https://onpack.io/api/v1/brands/milka/scans?since=2026-04-01T00:00:00Z&limit=50")
req = Net::HTTP::Get.new(uri, "Authorization" => "Bearer #{ENV['ONPACK_API_TOKEN']}")
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
scans = JSON.parse(res.body)["data"]
Retrieve one scan (with outcome)
Returns the same shape that the
scan.processed webhook delivers — so your ingestion code
can use one parser for both paths.
{
"scan": {
"id": 184293,
"unit": { "id": 9102, "code": "MK-CHO-260412-7HGQ2" },
"sku": { "id": 31, "name": "Milka Alpine Milk 100g", "prefix": "MK-CHO" },
"batch_id": 22,
"country": "DE", "city": "Berlin",
"device_type": "mobile",
"scanned_at": "2026-04-23T15:04:12Z"
},
"collector": { "id": 55821, "identified": true },
"eligibility": [
{
"activation": { "id": 8, "name": "Spring Instant Win", "activation_type": "instant_win" },
"outcome": "won",
"entry_id": 71284,
"reward": {
"id": 44, "name": "5€ voucher",
"reward_type": "coupon", "tier": "standard",
"face_value": 5.0
}
}
]
}
Collectors
The audience who has scanned at least one product from your brand. Strictly brand-scoped — scans on other brands are never surfaced here, even if the same consumer exists platform-wide.
id. Pair it with your own CRM if you need
contact info — ask for consent first.
List collectors
curl "https://onpack.io/api/v1/brands/milka/collectors?limit=20" \
-H "Authorization: Bearer pkd_••••••••••••••••"
Retrieve one collector
Returns brand-scoped activity: point balance, scan count, items claimed, plus the last 20 scans with their full outcomes. 404s if the collector has never scanned your brand.
{
"id": 55821,
"points": 320,
"scans": 12,
"items_claimed": 9,
"joined_at": "2026-02-11T08:44:00Z",
"recent_scans": [
{ "scan": { "...": "..." }, "collector": { "id": 55821, "identified": true }, "eligibility": [] }
]
}
Redemptions
Rewards that customers have claimed and redeemed at point-of-fulfilment.
Batches & SKUs
Generate and download unit codes for a SKU. Batches are async — you get a
batch.generated webhook when codes are ready and a
batch.export_ready webhook when the downloadable file is
available.
curl -X POST "https://onpack.io/api/v1/brands/milka/skus/31/batches" \
-H "Authorization: Bearer pkd_••••••••••••••••" \
-H "Content-Type: application/json" \
-d '{ "quantity": 100000, "purpose": "production" }'
Errors
Errors are JSON with an error string, sometimes accompanied by structured details.
{ "error": "Validation failed", "details": { "quantity": ["must be positive"] } }