Products

Products are read-only over the API - they are authored in ibakepro. There is no create, update, or delete endpoint. The API returns a safe catalog subset: never costs, recipes, margins, wholesale ladders, or any other internal data. Use it to power a headless storefront and to discover the product_id, variant_id, choice_id, and option_id values you need when building order lines.

Products endpoints accept both secret (ibp_sk_) and publishable (ibp_pk_) keys, and return CORS headers, so a browser storefront can read the catalog directly. Only products that are active, storefront-enabled, and not deleted are returned - drafts and archived products never appear, in either endpoint.


GET/api/v2/products

List products

List catalog products, cursor-paginated. Each entry is a light list item, not the full product - fetch the product by id for variants, choices, custom fields, and allergens.

Required scope: products:read

Query parameters

  • Name
    limit
    Type
    integer
    Description

    Page size. Default 50, maximum 100. Values above 100 are clamped.

  • Name
    cursor
    Type
    string
    Description

    Opaque cursor from pagination.next_cursor. A cursor whose product no longer exists returns 400 with code invalid_cursor rather than silently restarting at page 1 - restart the list without a cursor.

  • Name
    category
    Type
    string
    Description

    Exact-match filter on the product's category.

  • Name
    search
    Type
    string
    Description

    Case-insensitive "name contains" filter. Applied after the query, so it narrows the page rather than the index - use sort/cursor normally.

  • Name
    updated_after
    Type
    string
    Description

    ISO 8601 timestamp. Returns products whose updated_at is greater than or equal to this value. An unparseable value returns 400 with field updated_after. Supplying it forces sort=updatedAt regardless of what you pass in sort.

  • Name
    sort
    Type
    string
    Description

    name or updatedAt. Default name. Directions are fixed per field: name is always ascending, updatedAt is always descending. A leading - is stripped and any other value falls back to name. Note the value is updatedAt (camelCase) even though the response field is updated_at.

Incremental sync

updated_after returns only the products written since a given timestamp:

GET /api/v2/products?updated_after=<last_sync_iso>&sort=updatedAt

Page through with cursor. Two things the API does here:

  • The comparison is inclusive (>=), so a product whose updated_at equals the value supplied is returned again.
  • updated_at reflects any write to the product, including internal recalculations, so a delta pull can return a product whose visible data did not change.

A delta pull only ever returns visible products. A product that is archived, deleted, or storefront-disabled disappears from the list rather than arriving with a tombstone, and the detail endpoint returns 404 for it.

Request

GET
/api/v2/products
curl -G https://au.api.ibakepro.com/api/v2/products \
  -H "Authorization: Bearer {api_key}" \
  -d limit=10

Response

{
  "data": [
    {
      "id": "prod_ze9HI0w0LpVbemUI",
      "name": "Chocolate Birthday Cake",
      "slug": "chocolate-birthday-cake",
      "status": "active",
      "starting_from": 85,
      "image": "https://storage.googleapis.com/ibakepro-media/products/chocolate-birthday-cake.png",
      "category": "Cakes",
      "featured": false,
      "updated_at": "2026-06-04T07:17:34.854Z"
    }
  ],
  "pagination": {
    "limit": 10,
    "has_more": true,
    "next_cursor": "eyJpZCI6InByb2R1Y3QtMTc4MDMxNzI0ODQxMy0yOWNldmtiNm0ifQ",
    "prev_cursor": null
  }
}

GET/api/v2/products/:id

Retrieve a product

Read one product (full subset). Returns 404 if the product is missing, soft-deleted, not active, or storefront-disabled. The body is the product object itself, not wrapped in data.

Required scope: products:read

Field notes

  • Name
    status
    Type
    string
    Description

    Always active for anything the API returns. Other statuses 404.

  • Name
    slug
    Type
    string
    Description

    SEO slug, falling back to the product id when none is set.

  • Name
    images
    Type
    array
    Description

    Plain URL strings, in author order. Storage paths and original filenames are never exposed.

  • Name
    taxable
    Type
    boolean
    Description

    true unless explicitly turned off.

  • Name
    starting_from
    Type
    number
    Description

    The advertised "from" price: the cheapest variant price plus the unavoidable upcharge any required choice forces (its cheapest option). It is a display headline, not a line price - never send it as a price. Per-variant price is the raw base.

  • Name
    pricing_currency
    Type
    string
    Description

    ISO currency code for every amount on the product, resolved from the account's settings. Not present on list items.

  • Name
    has_variable_pricing
    Type
    boolean
    Description

    true only when the product has more than one variant and those variants have more than one distinct price. A multi-variant product priced identically across variants reports false.

  • Name
    sale_unit
    Type
    object
    Description

    How the product is sold: mode (each or weight), label, min, step. In each mode quantities are whole numbers with a minimum of 1. In weight mode quantities are fractional in units of label (for example kg), clamped to min and stepped by step, and the variant price is the price per unit of label - the line total is price x quantity.

  • Name
    variants
    Type
    array
    Description

    id, name, price, servings. id is what you send as the variant/size on an order line. price is the raw base for that variant and excludes every choice and custom-field upcharge. servings defaults to 1.

  • Name
    fulfilment_methods
    Type
    array
    Description

    The methods this product can be fulfilled by, a subset of pickup, delivery, dispatch, in that canonical order. A product that declares none falls back to ["pickup", "delivery"]. pickup_available, delivery_available, and dispatch_available are booleans derived from this list, so they can never disagree with it.

  • Name
    custom_fields
    Type
    array
    Description

    id, name, type, required, options (value, label, price_modifier), default_value. Supply order custom_fields keyed by id. Back-of-house fields are never exposed here.

  • Name
    default_value
    Type
    any
    Description

    The resolved prefill for a custom field, or null. A field the product defers to the customer emits null even when a default is configured, because prefilling it would satisfy the field at checkout and the deferred collection step would never fire. Treat null as "leave empty".

  • Name
    created_at
    Type
    string
    Description

    ISO 8601, or null on a document with no stored timestamp. Same for updated_at.

Choices

min_selections, max_selections, available, and depends_on are enforced server-side. The same rules run on POST /api/v2/checkout and on order creation, so a submission that breaks any of them is rejected with 400.

  • Name
    selection_type
    Type
    string
    Description

    single or multi.

  • Name
    min_selections
    Type
    integer
    Description

    Minimum options to select. single choices report 1. For multi, the minimum is clamped down to the number of options currently visible and available, so a "pick at least 3" choice with only 2 valid options requires 2.

  • Name
    max_selections
    Type
    integer
    Description

    Maximum options to select, or null for unbounded (again clamped to the visible option count). single choices report null; submit at most one option regardless.

  • Name
    required
    Type
    boolean
    Description

    When true, the choice must be answered - unless it is currently hidden by its dependency or has no available options, in which case it is skipped.

  • Name
    default_option_id
    Type
    string
    Description

    The option the baker configured as the default, or null. Also flagged as is_default on the option itself. It is not applied server-side: pricing resolves only the selections you send, so an optional choice you omit adds no upcharge, whether or not it has a default.

  • Name
    depends_on
    Type
    object
    Description

    null, or { choice_id, option_mappings }. choice_id is another choice on this same product (already resolved to its product-level id, so it matches a choices[].id directly). option_mappings maps a parent option id to the array of this choice's option ids allowed for that pick. Semantics: this choice does not apply until the parent is answered; a parent option absent from the map allows all of this choice's options; a parent option mapped to an empty array means this choice is not offered for that pick. Dependencies are single-level - a child is never itself a parent.

  • Name
    options[].price_modifier
    Type
    number
    Description

    Upcharge added to the line when that option is chosen, resolved for the product's default variant. Amounts can differ per variant on size-laddered options, and order create resolves the applicable amount server-side from the variant on the line.

  • Name
    options[].available
    Type
    boolean
    Description

    false means the option is disabled. Submitting it is rejected with 400.

  • Name
    options[].is_default
    Type
    boolean
    Description

    Whether this option is flagged as the default.

  • Name
    options[].allergens
    Type
    array
    Description

    Allergens the option itself introduces, in the same { allergen, level } shape as the product list, always at level contains. They are not merged into the product-level allergens array - union them with it when the option is selected.

Submitting a selection for a choice that is hidden by its dependency, has no available options, or does not exist on the product is rejected, as are duplicate option ids within one choice.

Allergens

allergens is a single flat array of { allergen, level }, where level is one of contains, may_contain, or processed_in. Names are canonical catalog names, so an author's "Nuts" surfaces as "Tree Nuts". The array is the union of everything declared on the product, kept at its strictest declared level.

This is declared data. An empty array means nothing has been declared for the product, not that the product is free of allergens. Do not present it as an allergen-free claim, and remember to add the allergens carried by any selected choice options.

Request

GET
/api/v2/products/prod_ze9HI0w0LpVbemUI
curl https://au.api.ibakepro.com/api/v2/products/prod_ze9HI0w0LpVbemUI \
  -H "Authorization: Bearer {api_key}"

Response

{
  "id": "prod_ze9HI0w0LpVbemUI",
  "name": "Chocolate Birthday Cake",
  "description": "Rich chocolate sponge layered with ganache.",
  "status": "active",
  "slug": "chocolate-birthday-cake",
  "images": [ "https://storage.googleapis.com/ibakepro-media/products/chocolate-birthday-cake.png" ],
  "category": "Cakes",
  "featured": false,
  "taxable": true,
  "allergens": [
    { "allergen": "Peanuts", "level": "contains" },
    { "allergen": "Sesame", "level": "may_contain" }
  ],
  "starting_from": 85,
  "pricing_currency": "AUD",
  "has_variable_pricing": false,
  "sale_unit": { "mode": "each", "label": "", "min": 1, "step": 1 },
  "variants": [
    { "id": "base", "name": "Chocolate Birthday Cake", "price": 85, "servings": 12 }
  ],
  "choices": [
    {
      "id": "cho_hoFEGdk3rJ2waVe3",
      "name": "Flavour",
      "required": true,
      "selection_type": "single",
      "min_selections": 1,
      "max_selections": null,
      "default_option_id": "opt_rep1BQgeu8dFF6FF",
      "depends_on": null,
      "options": [
        { "id": "opt_rep1BQgeu8dFF6FF", "name": "Classic chocolate", "price_modifier": 0,
          "allergens": [], "available": true, "is_default": true },
        { "id": "opt_JiK6AYdC3lARutKE", "name": "Salted caramel", "price_modifier": 12,
          "allergens": [], "available": true, "is_default": false }
      ]
    },
    {
      "id": "cho_A2Mh6IsNeDR3JPCa",
      "name": "Filling",
      "required": false,
      "selection_type": "multi",
      "min_selections": 1,
      "max_selections": 2,
      "default_option_id": null,
      "depends_on": {
        "choice_id": "cho_hoFEGdk3rJ2waVe3",
        "option_mappings": { "opt_rep1BQgeu8dFF6FF": [ "opt_B4TrgBOI2vmwQzKW" ] }
      },
      "options": [
        { "id": "opt_B4TrgBOI2vmwQzKW", "name": "Raspberry jam", "price_modifier": 0,
          "allergens": [], "available": true, "is_default": false }
      ]
    }
  ],
  "custom_fields": [
    { "id": "7RAxjEhG0Y7mGUKzxa5Q", "name": "Icing colour", "type": "select",
      "required": false,
      "options": [
        { "value": "white", "label": "White", "price_modifier": 0 },
        { "value": "gold", "label": "Gold leaf", "price_modifier": 15 }
      ],
      "default_value": null }
  ],
  "fulfilment_methods": [ "pickup", "delivery" ],
  "pickup_available": true,
  "delivery_available": true,
  "dispatch_available": false,
  "created_at": "2026-06-01T12:34:10.143Z",
  "updated_at": "2026-06-04T07:17:34.854Z"
}

For instant, type-as-you-go search from the browser, mint a scoped search token instead of paging this endpoint. See Product Search.