Skip to main content
A Product is a row in your Awardee catalog, keyed by GTIN, with a routable destination, typed key/value properties, and a gallery of product images. Products power three jobs:

Catalog mirror

Sync your ERP, PIM, or e-commerce catalog into Awardee. One product per GTIN, kept fresh through PATCH.

Packaging-to-content

A consumer scans the GS1 barcode on the pack. The scan resolves the GTIN against your catalog and routes them to a chatbot, page, or knowledge base.

Retail integrations

Look up a product by raw GTIN from a POS, scanner, or mobile app via GET /products/by-gtin/{gtin}.

Lifecycle

A product moves through four statuses:
StatusMeaning
unconfiguredCreated but no destination set. Default on POST. Scans land on a “not configured” screen.
activeLive. GTIN scans resolve to the configured destination.
inactivePaused. The row stays, scans show a “paused” screen. Useful for seasonal products.
archivedSoft-deleted. The row is hidden from list views but historical scans keep working for audits.
A product becomes active as soon as a destination is set (the dashboard does this automatically; the API leaves it to you). Flip back to inactive or archived whenever — the row is never lost.

GTIN normalization

The gtin field is always stored and returned in canonical GTIN-14 form, left-padded with zeros. On write the value must be exactly 8, 12, 13, or 14 digits — GTIN-8, GTIN-12 (UPC-A), GTIN-13 (EAN-13), or GTIN-14. Anything else (a 9- or 11-digit string, non-digit characters) is rejected with 400 invalid_payload. Valid input is normalized to GTIN-14 before persisting. Pass null to clear a stored GTIN.
This matters because GS1 Digital Link readers silently pad to 14 digits when parsing. Storing the canonical form means a 13-digit EAN scanned from a pack and a 12-digit UPC typed into your ERP resolve to the same row.
0012345 678905  →  00012345678905
12345678905     →  00012345678905
123456789012    →  00123456789012  ← 12-digit UPC-A, padded
9300675024235   →  09300675024235  ← 13-digit EAN-13, padded
00012345678905  →  00012345678905  ← already canonical
The check digit is not verified on write. Operators paste codes with typos and we’d rather surface that as a duplicate-detection error than reject silently. If you need strict validation, run it client-side before posting.

Anatomy

{
  "object": "product",
  "id": "8f3a9d2e-1b4c-4f5d-9e8a-7c3b2a1d0f9e",
  "name": "Reserve Cabernet Sauvignon 2021",
  "gtin": "09300675024235",
  "description": "Single-vineyard reserve from the Yarra Valley.",
  "status": "active",
  "destination": {
    "type": "page",
    "chatbot": null,
    "page": "a1c2e4f6-8b0d-4e2a-9c1b-3d5e7f9a2c4e",
    "knowledge_base": null,
    "url": null
  },
  "metadata": { "vintage": 2021, "abv": 14.2 },
  "total_scans": 142,
  "last_scanned_at": "2026-05-22T14:08:31+00:00",
  "created_at": "2025-11-04T03:22:01+00:00",
  "updated_at": "2026-05-22T14:08:31+00:00"
}
Two satellite resources hang off each product:
  • Properties — typed K/V rows (vintage, region, case_size). Replaced as a set via PUT /products/{id}/properties.
  • Images — uploaded photos. See Images below.

CRUD workflow

A typical first sync from your system:
1

Pull your catalog

Iterate every SKU in your source of truth. You’ll need at minimum a name and a GTIN; everything else is optional.
2

Create products

POST /products for each SKU. Set status: "active" if you’re shipping a destination at the same time, otherwise leave it default and configure later. Pass image_urls to attach images in the same call (see Images).
curl https://api.awardee.dev/v1/products \
  -H "Authorization: Bearer aw_live_4f8a3c7e2d1b9a5c6f8e3d2c1b4a9f7e" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Reserve Cabernet Sauvignon 2021",
    "gtin": "9300675024235",
    "destination": {
      "type": "page",
      "page": "a1c2e4f6-8b0d-4e2a-9c1b-3d5e7f9a2c4e"
    },
    "status": "active",
    "metadata": { "vintage": 2021, "abv": 14.2 }
  }'
3

Attach properties

PUT /products/{id}/properties replaces the property set atomically. Send the full desired state — Awardee upserts it, then drops only the keys you left out, so a failed request never destroys your existing properties. A body that repeats a key is rejected with 422 unprocessable before anything is written.
4

Attach images

POST /products/{id}/images with a public URL — or pass image_urls on the create call to handle them in one shot. See Images.
5

Reconcile on changes

On every catalog change, look the product up by GTIN (GET /products/by-gtin/{gtin}) and PATCH the fields that drift. If the GTIN doesn’t exist, POST a new one.
Use the metadata field for anything that doesn’t deserve a first-class column or a property row. It’s a free-form JSON object — your vintage, abv, internal SKU codes, feature flags, etc. It’s returned verbatim on every read.

Images

Send a public image URL and Awardee fetches the bytes (SSRF-checked), stores them in your organization’s asset library, and links the resulting asset to the product. The API never accepts raw binary — always pass a URL we can reach over HTTPS. Asset bytes are content-addressed and de-duplicated within the organization: attaching the same image to multiple products stores it once. The file_url returned for two products with the same image will be identical, and the storage cost is paid once. There are two ways to attach images:
  • Bulk on create. Pass image_urls: ["…", "…"] on POST /products. URLs are stored in the order given.
  • Incremental. POST /products/{id}/images with one URL at a time. Use this to add a single image to an existing product, or to control sort_order per image.
Either path returns a product_image row per image once it’s stored. Subsequent reads expose images via GET /products/{id}/images and you can remove one with DELETE /products/{id}/images/{imageId}.

Accepted formats and size

JPEG, PNG, WebP, and animated GIF up to 15 MiB each. The content is sniffed from the bytes — content-type headers alone aren’t trusted.

Bulk on create

cURL
curl https://api.awardee.dev/v1/products \
  -H "Authorization: Bearer aw_live_4f8a3c7e2d1b9a5c6f8e3d2c1b4a9f7e" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Reserve Cabernet Sauvignon 2021",
    "gtin": "9300675024235",
    "image_urls": [
      "https://cdn.example.com/products/reserve-cab/front.jpg",
      "https://cdn.example.com/products/reserve-cab/back.jpg"
    ]
  }'
If any URL fails (unreachable, blocked, oversized, not a real image), the product is still created. Failed URLs come back on the response under failed_image_urls. The field is omitted entirely when every URL succeeded.
{
  "object": "product",
  "id": "8f3a9d2e-1b4c-4f5d-9e8a-7c3b2a1d0f9e",
  "name": "Reserve Cabernet Sauvignon 2021",
  "...": "…",
  "failed_image_urls": [
    "https://cdn.example.com/products/reserve-cab/back.jpg"
  ]
}

Incremental attach

cURL
curl https://api.awardee.dev/v1/products/8f3a9d2e-1b4c-4f5d-9e8a-7c3b2a1d0f9e/images \
  -H "Authorization: Bearer aw_live_4f8a3c7e2d1b9a5c6f8e3d2c1b4a9f7e" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://cdn.example.com/products/reserve-cab/detail.jpg",
    "sort_order": 2
  }'
The response is the new image row:
{
  "object": "product_image",
  "id": "d5e6f7a8-9b0c-4d1e-2f3a-4b5c6d7e8f9a",
  "product_id": "8f3a9d2e-1b4c-4f5d-9e8a-7c3b2a1d0f9e",
  "file_url": "https://files.awardee.dev/orgs/3c1e.../library/9f/9f2a48b1c7e4d5...jpg",
  "file_name": "detail.jpg",
  "media_type": "image/jpeg",
  "size_bytes": 184320,
  "sort_order": 2,
  "created_at": "2025-11-04T03:23:11+00:00"
}
Some CDNs (Shopify, Akamai, etc.) block non-browser fetches. If a URL keeps failing, host the bytes somewhere reachable — S3, your own CDN — and pass that URL instead.

Ordering

sort_order controls display order on the product. Lower values come first. When using image_urls, the array order is used directly. There’s no first-class reorder endpoint today — to change the order, delete and re-attach with explicit sort_order values.

Delete an image

DELETE /products/{id}/images/{imageId} detaches the image from the product. Returns 204 No Content. The underlying asset bytes stay in your organization’s library and may still be referenced by other products or features; orphan assets (zero references) are reclaimed by a background GC job. Deleting the product itself detaches every image attached to it.

Webhook events

EventFires on
product.createdPOST /products
product.updatedPATCH /products/{id}
product.deletedDELETE /products/{id}
product.scannedA GS1 Digital Link scan resolved this product.
Subscribe and verify signatures per the webhooks guide.