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

GET/brandsList your brands
GET/brands/:slug/scansRecent scans
GET/brands/:slug/scans/:idOne scan with full outcome
GET/brands/:slug/collectorsAudience that scanned this brand
GET/brands/:slug/collectors/:idOne collector with history
GET/brands/:slug/redemptionsReward redemptions
GET/brands/:slug/skusList SKUs
POST/brands/:slug/skusCreate a SKU
POST/brands/:slug/skus/:id/batchesGenerate a batch of codes
GET/brands/:slug/skus/:sku_id/batches/:id/downloadDownload batch CSV

Scans

Every touchpoint with your packaging. Production scans only — tester activity is excluded.

List scans

GET/brands/:slug/scans
ParamTypeDescription
sinceISO 8601Return scans created on or after this timestamp.
untilISO 8601Return scans created on or before this timestamp.
limitint, ≤1000Default 100.
offsetintDefault 0.
curl
curl "https://onpack.io/api/v1/brands/milka/scans?since=2026-04-01T00:00:00Z&limit=50" \
  -H "Authorization: Bearer pkd_••••••••••••••••"
Ruby
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)

GET/brands/:slug/scans/:id

Returns the same shape that the scan.processed webhook delivers — so your ingestion code can use one parser for both paths.

JSON
{
  "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.

i
Email and other PII are intentionally omitted. Consumer identity surfaces as an opaque id. Pair it with your own CRM if you need contact info — ask for consent first.

List collectors

GET/brands/:slug/collectors
curl
curl "https://onpack.io/api/v1/brands/milka/collectors?limit=20" \
  -H "Authorization: Bearer pkd_••••••••••••••••"

Retrieve one collector

GET/brands/:slug/collectors/:id

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.

JSON
{
  "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.

GET/brands/:slug/redemptions

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.

GET/brands/:slug/skus
POST/brands/:slug/skus
POST/brands/:slug/skus/:id/batches
GET/brands/:slug/skus/:sku_id/batches/:id/download
curl
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.

JSON
{ "error": "Validation failed", "details": { "quantity": ["must be positive"] } }