Products
Products are definitions of physical products in our webshop. These products are usually supplied and delivered by the organization itself.
Backoffice sync (October 2026): use POST /products/batch to push up to 500 products per call from your own backoffice or ERP. Products are matched on your own external_id, so replaying the same batch is safe. The list endpoint can now be filtered on organization_id, external_id and integration_id, and paginated with page / per_page.
Abilities
Every endpoint on this page requires a token with the matching ability. The products.* abilities are the current names; tokens with the legacy product.* abilities keep working on the same routes. A token missing the ability gets an HTTP 403.
- Name
products.get- Type
- ability
- Description
List and inspect products. Legacy:
product.get.
- Name
products.store- Type
- ability
- Description
Create a single product and batch-upsert products. Legacy:
product.store.
- Name
products.update- Type
- ability
- Description
Update a product. Legacy:
product.update.
- Name
products.delete- Type
- ability
- Description
Archive and restore a product. Legacy:
product.delete.
Everything is scoped to the organizations the token's user has access to. Products of other organizations are never returned or modified.
The products model
The products model contains information about the product itself and extra information regarding webshop availability. The model is defined as follows:
Properties
- Name
identifier- Type
- string
- Description
Unique identifier for the product.
- Name
price- Type
- float
- Description
The sale price of the product.
- Name
points- Type
- integer
- Description
The sale amount of points of the product.
- Name
show_title- Type
- boolean
- Description
whether or not to show the title in the webshop.
- Name
show_subtitle- Type
- boolean
- Description
whether or not to show the subtitle in the webshop.
- Name
organization- Type
- object
- Description
Contains in formation about the products organization. contains:
['identifier', 'name']
- Name
primary_color- Type
- string
- Description
A Hexadecimal color code for the primary color of the product (background color for the webshop).
- Name
text_color- Type
- string
- Description
A Hexadecimal color code for the text color of the product (text color for the webshop).
- Name
ribbon_color- Type
- string
- Description
A Hexadecimal color code for the ribbon color of the product (label color for the webshop).
- Name
title- Type
- string
- Description
The title of the product.
- Name
subtitle- Type
- string
- Description
The subtitle of the product.
- Name
thumbnail_url- Type
- string|url
- Description
The URL to the image of the product.
- Name
start_date- Type
- timestamp
- Description
Used to set the availablity of the product in the webshop.
- Name
end_date- Type
- timestamp
- Description
Used to set the availablity of the product in the webshop.
- Name
use_start_and_end- Type
- boolean
- Description
whether or not to use a start and end date in the webshop.
- Name
sale- Type
- boolean
- Description
whether or not the product is on sale.
- Name
sale_points- Type
- integer
- Description
The sale amount of points of the product.
- Name
sale_price- Type
- float
- Description
The sale price of the product.
- Name
sale_text- Type
- string
- Description
The text to show when the product is on sale.
- Name
stock- Type
- integer
- Description
The amount of stock of the product.
- Name
use_stock- Type
- boolean
- Description
whether or not to use a stock in the webshop.
- Name
type- Type
- string
- Description
pickupordelivery.
- Name
vat- Type
- float
- Description
The VAT percentage of the product (for example
21).
- Name
purchase_price- Type
- float|null
- Description
The purchase (cost) price of the product.
- Name
b2b_price_cents- Type
- integer|null
- Description
The B2B price of the product in cents.
nullwhen no B2B price is set.
- Name
external_id- Type
- string|null
- Description
Your own identifier of the product (for example a SKU). Set by
POST /products/batch.
- Name
integration_id- Type
- integer|null
- Description
The integration the product was pushed by. Together with the organization and
external_idthis is the upsert key of the batch endpoint.nullfor products created in the dashboard, the marketplace or Shopify.
- Name
archived- Type
- boolean
- Description
Whether the product is archived. Archived products are not available in the webshop.
- Name
created_at- Type
- timestamp
- Description
A timestamp when the product was created.
- Name
updated_at- Type
- timestamp
- Description
A timestamp when the product was last updated.
Note
Because a user can have access to multiple organizations we require a organization_id to be provided for most requests. We will also return an organization reference in each response.
List all products
This endpoint returns a list of all the products the currently authenticated user has access to, ordered by creation (oldest first). Archived products are included; check the archived flag. The description is not part of the list output; use Inspect a product for it.
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
products.get- Type
- string
- Description
The scope required to access this endpoint (legacy
product.getis also accepted).
Query parameters
- Name
organization_id- Type
- string
- Description
Optional. Only return products of this organization (organization identifier).
- Name
external_id- Type
- string
- Description
Optional. Only return products with this
external_id.
- Name
integration_id- Type
- integer
- Description
Optional. Only return products pushed by this integration (for example
0, the default of the batch endpoint).
- Name
page- Type
- integer
- Description
Optional. Page number, starting at
1. Pagination is only applied whenpageorper_pageis present.
- Name
per_page- Type
- integer
- Description
Optional. Products per page, default
100, maximum500(higher values are capped at 500).
Without page and per_page all matching products are returned in one response, without meta. With either of them the response contains a meta object (current_page, per_page, total, last_page). For large catalogues always paginate.
Request
curl -X GET https://api.walletapp.co/products
-H "Content-Type: application/json"
-H "Accept: application/json"
-H "Authorization: Bearer <your_token>"
Response
{
"message": "Request successful",
"data": [
{
"created_at": "<timestamp>",
"end_date": "<timestamp>",
"identifier": "<product_id>",
"organization": {
"name": "<organization_name>",
"identifier": "<organization_id>"
},
"points": 0,
"price": 0,
"primary_color": "<hex_color>",
"sale": 0,
"sale_points": 0,
"sale_price": 0.00,
"sale_text": "<sale_text>",
"show_subtitle": 0,
"show_title": 0,
"start_date": "<timestamp>",
"stock": 0,
"subtitle": "<subtitle>",
"text_color": "<hex_color>",
"thumbnail_url": "<url>",
"title": "<title>",
"updated_at": "<timestamp>",
"use_start_and_end": 0,
"use_stock": 0
}
],
...
}
Filtered + paginated
curl -X GET "https://api.walletapp.co/products?organization_id=<organization_id>&integration_id=0&page=1&per_page=500" \
-H "Accept: application/json" \
-H "Authorization: Bearer <your_token>"
Response (paginated)
{
"message": "Request successful",
"data": [
{
"archived": 0,
"b2b_price_cents": 1000,
"external_id": "SKU-1",
"identifier": "<product_id>",
"integration_id": 0,
"organization": {
"name": "<organization_name>",
"identifier": "<organization_id>"
},
"price": 12.5,
"purchase_price": "6.00",
"title": "Wine",
"type": "pickup",
"vat": "21.00",
...
}
],
"meta": {
"current_page": 1,
"per_page": 500,
"total": 1240,
"last_page": 3
}
}
Create a product
This endpoint allows you to create a single product. To create or update many products from a backoffice, use POST /products/batch instead.
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
products.store- Type
- string
- Description
The scope required to access this endpoint (legacy
product.storeis also accepted).
Request body
- Name
title- Type
- string|Required
- Description
The title of the product.
- Name
subtitle- Type
- string|Required
- Description
The subtitle of the product.
- Name
organization_id- Type
- string|Required
- Description
The identifier of the organization the product should be created for.
- Name
description- Type
- string
- Description
The description of the product.
- Name
thumbnail_url- Type
- string|url
- Description
A valid url to the thumbnail image of the product.
- Name
show_title- Type
- boolean
- Description
Whether or not to show the title of the product.
- Name
show_subtitle- Type
- boolean
- Description
Whether or not to show the subtitle of the product.
- Name
price- Type
- float
- Description
The webshop price of the product.
- Name
points- Type
- integer
- Description
The webshop amount of points the product should cost.
- Name
use_stock- Type
- boolean
- Description
Whether or not the product makes use of stock.
- Name
stock- Type
- integer|Required
- Description
The amount of stock the product has.
- Name
use_start_and_end- Type
- boolean
- Description
Whether or not the product makes use of start and end dates.
- Name
sale- Type
- boolean
- Description
Whether or not the product is on sale.
- Name
sale_price- Type
- float
- Description
The sale price of the product.
- Name
sale_points- Type
- integer
- Description
The sale amount of points the product should cost.
- Name
sale_text- Type
- string
- Description
The sale text of the product.
- Name
start_date- Type
- timestamp
- Description
The start date of the product.
- Name
end_date- Type
- timestamp
- Description
The end date of the product.
- Name
type- Type
- string
- Description
Optional. Can be
pickupordelivery. Usedeliveryfor physical products.
- Name
shopify_id- Type
- string
- Description
Optional. A unique identifier that links the product to a Shopify product.
Request
curl -X POST https://api.walletapp.co/products \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer <your_token>" \
-d '{
"title": "<title>",
"subtitle": "<subtitle>",
"organization_id": "<organization_id>",
"description": "<description>",
"thumbnail_url": "<image_url>",
"show_title": true,
"show_subtitle": true,
"price": 12.50,
"points": 123,
"use_stock": true,
"stock": 12,
"use_start_and_end": true,
"start_date": "2022-01-01",
"end_date": "2023-01-01",
"sale": true,
"sale_price": 10.00,
"sale_points": 100,
"sale_text": "10% Off",
"type": "delivery",
"shopify_id": "123456789"
}'
Response
{
"message": "Request successful",
"data": {
"identifier": "<product_id>",
"title": "<title>",
"subtitle": "<subtitle>",
"organization": {
"name": "<organization_name>",
"identifier": "<organization_id>"
},
"thumbnail_url": "<url>",
"description": "<description>",
"price": 12.5,
"points": 123,
"stock": 12,
"sale": true,
"sale_price": 10.0,
"sale_points": 100,
"sale_text": "10% Off",
"show_title": true,
"show_subtitle": true,
"start_date": "2022-01-01",
"end_date": "2023-01-01",
"use_stock": true,
"use_start_and_end": true,
"type": "delivery",
"shopify_id": "gid://shopify/Product/123456789",
"created_at": "<timestamp>",
"updated_at": "<timestamp>"
}
}
Batch upsert products
This endpoint creates or updates up to 500 products of one organization in a single call. It is meant for keeping the product catalogue in sync with your own backoffice or ERP.
Each product is matched on the upsert key: the organization, the integration_id (default 0) and your external_id. A product that does not exist for that key yet is created; an existing one is updated. Products created in the dashboard, the marketplace or Shopify (no integration_id) are never matched, so this endpoint cannot touch them.
Every row is validated and saved on its own: a bad row never fails the rest of the batch. The response always has HTTP 200 and contains a result per row.
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
products.store- Type
- string
- Description
The scope required to access this endpoint (legacy
product.storeis also accepted).
Request body
- Name
organization_id- Type
- string|Required
- Description
The identifier of the organization the products belong to.
- Name
integration_id- Type
- integer
- Description
Optional, default
0. A non-negative integer that identifies your integration. Use a different value per source system if more than one system pushes products for the same organization; each integration has its ownexternal_idnamespace.
- Name
products- Type
- array|Required
- Description
1 to 500 product rows. Each row is an object with the fields below.
Product row
- Name
external_id- Type
- string|Required
- Description
Your own identifier of the product (for example a SKU), max 191 characters. Must be unique within the batch; a repeated
external_idfails as a duplicate.
- Name
title- Type
- string
- Description
Required when the product is created. Max 191 characters.
- Name
price- Type
- float
- Description
Required when the product is created. The sale price (0 – 9999999).
- Name
vat- Type
- float
- Description
Required when the product is created. The VAT percentage (0 – 100).
- Name
subtitle- Type
- string|null
- Description
The subtitle, max 191 characters.
- Name
description- Type
- string|null
- Description
The description as plain text, max 20000 characters.
- Name
b2b_price_cents- Type
- integer|null
- Description
The B2B price in cents (0 – 4294967295). Send
nullto clear it.
- Name
purchase_price- Type
- float|null
- Description
The purchase (cost) price (0 – 9999999).
- Name
type- Type
- string
- Description
pickupordelivery, max 50 characters.
- Name
thumbnail_url- Type
- string|url|null
- Description
A URL to the product image, max 2048 characters. Stored as given (the image is not downloaded).
- Name
archived- Type
- boolean
- Description
Archive (
true) or restore (false) the product.
On an update only the keys present in the row are written; keys you leave out keep their current value. There is no stock field: stock is not synchronised through this endpoint.
Row status
- Name
created- Type
- status
- Description
A new product was created.
identifieris the new product identifier.
- Name
updated- Type
- status
- Description
An existing product was found and at least one field changed.
- Name
unchanged- Type
- status
- Description
An existing product was found and all sent fields already had these values. Nothing was written.
- Name
failed- Type
- status
- Description
The row was not saved.
reasonexplains why (for example a validation message or a duplicateexternal_id).
Idempotent replay
Sending the same batch again gives the same end state: rows that were created or updated the first time are reported as unchanged. A batch that timed out on your side can therefore safely be retried.
Errors
- Name
401- Type
- Unauthorized
- Description
The
organization_idis missing, unknown, or not accessible with this token. Unknown and foreign organizations return the same response.
- Name
403- Type
- Forbidden
- Description
The token does not have the
products.store(or legacyproduct.store) ability.
- Name
422- Type
- Unprocessable
- Description
productsis missing, empty, not a list, or has more than 500 rows; orintegration_idis not a non-negative integer. Nothing is processed.
Row-level problems never produce a 4xx; they are reported as failed rows in a 200 response.
Request
curl -X POST https://api.walletapp.co/products/batch \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer <your_token>" \
-d '{
"organization_id": "<organization_id>",
"integration_id": 0,
"products": [
{
"external_id": "SKU-1",
"title": "Wine",
"price": 12.50,
"vat": 21,
"b2b_price_cents": 1000,
"purchase_price": 6,
"subtitle": "<subtitle>",
"description": "<description>",
"type": "pickup",
"thumbnail_url": "https://example.com/sku-1.png",
"archived": false
},
{
"external_id": "SKU-2",
"price": 9.95
}
]
}'
Response
{
"message": "Request successful",
"data": {
"summary": {
"total": 2,
"created": 1,
"updated": 0,
"unchanged": 0,
"failed": 1
},
"results": [
{
"index": 0,
"external_id": "SKU-1",
"status": "created",
"identifier": "<product_id>"
},
{
"index": 1,
"external_id": "SKU-2",
"status": "failed",
"reason": "The title field is required."
}
]
}
}
Response (422, too many rows)
{
"message": "The provided request was invalid (errors: 1)",
"errors": {
"products": "A batch can contain at most 500 products"
}
}
Inspect a product
The endpoint allows you to fetch a product by its identifier. Archived products are not returned (HTTP 422); restore them first or find them through List all products.
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
products.get- Type
- string
- Description
The scope required to access this endpoint (legacy
product.getis also accepted).
Route parameters
- Name
identifier- Type
- string
- Description
The identifier of the product you want to fetch.
Request
curl -X GET https://api.walletapp.co/products/:identifier
-H "Content-Type: application/json"
-H "Accept: application/json"
-H "Authorization: Bearer <your_token>"
Response
{
"message": "Request successful",
"data": {
"created_at": "<timestamp>",
"end_date": "<timestamp>",
"identifier": "<product_id>",
"organization": {
"name": "<organization_name>",
"identifier": "<organization_id>"
},
"points": 0,
"price": 0,
"primary_color": "<hex_color>",
"sale": 0,
"sale_points": 0,
"sale_price": 0.00,
"sale_text": "<sale_text>",
"show_subtitle": 0,
"show_title": 0,
"start_date": "<timestamp>",
"stock": 0,
"subtitle": "<subtitle>",
"text_color": "<hex_color>",
"thumbnail_url": "<url>",
"title": "<title>",
"updated_at": "<timestamp>",
"use_start_and_end": 0,
"use_stock": 0
}
}
Update a product
This endpoint allows you to modify a product. Only the fields you send are changed.
Products pushed by a backoffice (products with an external_id and an integration_id, created through POST /products/batch) are updated through the same upsert logic as the batch endpoint. For those products only title, subtitle, description, price, b2b_price_cents, purchase_price, archived, type and thumbnail_url are applied, the description is stored as plain text and the response is {"identifier": "<product_id>", "status": "updated"} (or "unchanged"). A row that fails validation returns HTTP 422 with the reason. Marketplace products (an external_id without an integration_id) cannot be updated (HTTP 422).
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
products.update- Type
- string
- Description
The scope required to access this endpoint (legacy
product.updateis also accepted).
Route parameters
- Name
identifier- Type
- string
- Description
The identifier of the product.
Request body
- Name
title- Type
- string
- Description
The title of the product.
- Name
subtitle- Type
- string
- Description
The subtitle of the product.
- Name
organization_id- Type
- string
- Description
The identifier of the organization the product should be created for.
- Name
description- Type
- string
- Description
The description of the product.
- Name
thumbnail_url- Type
- string|url
- Description
A valid url to the thumbnail image of the product.
- Name
show_title- Type
- boolean
- Description
whether or not to show the title of the product.
- Name
show_subtitle- Type
- boolean
- Description
whether or not to show the subtitle of the product.
- Name
price- Type
- float
- Description
The webshop price of the product.
- Name
points- Type
- integer
- Description
The webshop amount of points the product should cost.
- Name
use_stock- Type
- boolean
- Description
whether or not the product makes use of stock.
- Name
stock- Type
- integer
- Description
The amount of stock the product has.
- Name
use_start_and_end- Type
- boolean
- Description
whether or not the product makes use of start and end dates.
- Name
sale- Type
- boolean
- Description
whether or not the product is on sale.
- Name
sale_price- Type
- float
- Description
The sale price of the product.
- Name
sale_points- Type
- integer
- Description
The sale amount of points the product should cost.
- Name
sale_text- Type
- string
- Description
The sale text of the product.
- Name
start_date- Type
- timestamp
- Description
The start date of the product.
- Name
end_date- Type
- timestamp
- Description
The end date of the product.
- Name
type- Type
- string
- Description
pickupordelivery.
- Name
shopify_id- Type
- string
- Description
A unique identifier that links the product to a Shopify product.
- Name
b2b_price_cents- Type
- integer|null
- Description
The B2B price in cents (0 – 4294967295).
- Name
purchase_price- Type
- float|null
- Description
The purchase (cost) price of the product.
Request
curl -X PUT https://api.walletapp.co/products/:identifier
-H "Content-Type: application/json"
-H "Accept: application/json"
-H "Authorization: Bearer <your_token>"
-d '{
"title":"<title>",
"subtitle":"<subtitle>",
"description":"<description>",
"organization_id":"<organization_id>",
"thumbnail_url":"<image_url>",
"show_title": true,
"price": 12.50,
"primary_color":"#ff00ff",
"text_color":"#0000ff",
"show_subtitle":true,
"points": 123,
"use_stock":true,
"use_start_and_end": true,
"sale": true,
"sale_text": "<sale_text>",
"sale_price":12.50,
"sale_points":12,
"stock":12,
"start_date":"2022-01-01",
"end_date":"2023-01-01",
"type": "delivery",
"shopify_id": "123456789"
}'
Response
{
"message": "Request successful",
"data": {
"created_at": "<timestamp>",
"end_date": "<timestamp>",
"identifier": "<product_id>",
"organization": {
"name": "<organization_name>",
"identifier": "<organization_id>"
},
"points": 0,
"price": 0,
"primary_color": "<hex_color>",
"sale": 0,
"sale_points": 0,
"sale_price": 0.00,
"sale_text": "<sale_text>",
"show_subtitle": 0,
"show_title": 0,
"start_date": "<timestamp>",
"stock": 0,
"subtitle": "<subtitle>",
"text_color": "<hex_color>",
"thumbnail_url": "<url>",
"title": "<title>",
"updated_at": "<timestamp>",
"use_start_and_end": 0,
"use_stock": 0
}
}
Archive a product
The endpoint archives a product. The product is not deleted: it disappears from the webshop and can be brought back with Restore a product. Archiving an already archived product returns HTTP 422. Marketplace products (an external_id without an integration_id) cannot be archived (HTTP 422).
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
products.delete- Type
- string
- Description
The scope required to access this endpoint (legacy
product.deleteis also accepted).
Route parameters
- Name
identifier- Type
- string
- Description
The identifier of the product.
Request
curl -X DELETE https://api.walletapp.co/products/:identifier
-H "Content-Type: application/json"
-H "Accept: application/json"
-H "Authorization: Bearer <your_token>"
Response
{
"message": "Request successful",
"data": "Product archived"
}
Restore a product
The endpoint restores an archived product. Restoring a product that is not archived returns HTTP 422. Marketplace products cannot be restored through the API (HTTP 422).
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
products.delete- Type
- string
- Description
The scope required to access this endpoint (legacy
product.deleteis also accepted).
Route parameters
- Name
identifier- Type
- string
- Description
The identifier of the product.
Request
curl -X PATCH https://api.walletapp.co/products/:identifier
-H "Content-Type: application/json"
-H "Accept: application/json"
-H "Authorization: Bearer <your_token>"
Response
{
"message": "Request successful",
"data": "Product restored"
}
