Passes
Passes are a way to provide your users with a digital representation of a physical product. This can be a coupon, a storecard or a prepaidcard.
Inspect pass with email
Search for a pass using a members email address for a specific brand.
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
passes.get- Type
- string
- Description
The scope required to access this endpoint.
Query parameters
- Name
email- Type
- string|Required
- Description
The email address of the member.
- Name
brand_id- Type
- string|Required
- Description
The UUID of the brand.
- Name
pass_code- Type
- string|Required
- Description
The code of the pass.
Request
curl -X GET https://api.walletapp.co/passes
-H "Content-Type: application/json"
-H "Accept: application/json"
-H "Authorization: Bearer <your_token>"
-d '{
"brand_id": "<brand_id>",
"email": "<email>",
"pass_code": "<pass_id||qr_code>"
}'
Response
{
"success": true,
"data": {
"brand_id": "<brand_id>",
"code": "<code>",
"created_at": "<timestamp>",
"updated_at": "<timestamp>",
"demo": 0,
"redeemed": 0,
"active": 1,
"expired": 0,
"blocked": 0,
"reminder_send": 0,
"expiration_date": "<timestamp>",
"start_date": null,
"reminder_date": null,
"parsed_historical": 0,
"coupon": {
"identifier": "<identifier>",
"price": "<coupon shop price>",
"points": "<coupon shop points>",
"external_reference": "<reference>"
},
"storecard": {
"identifier": "<identifier>",
"points": "<points>",
"balance": "<balance>",
}
}
}
Inspect passes
Search for a pass only using the pass code.
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
search.pass- Type
- string
- Description
The scope required to access this endpoint.
Query parameters
- Name
pass_code- Type
- string|Required
- Description
The code of the pass.
Request
curl -X GET https://api.walletapp.co/search/pass
-H "Content-Type: application/json"
-H "Accept: application/json"
-H "Authorization: Bearer <your_token>"
-d '{
"pass_code": "<pass_id||qr_code>"
}'
Response
{
"success": true,
"data": [
{
"created_at": "<timestamp>",
"updated_at": "<timestamp>",
"expiration_date": "<timestamp>",
"downloaded": 1,
"start_date": "<timestamp>",
"batch_reference": null,
"reminder_date": null,
"download_lat": null,
"user_name": "<username>",
"member_id": "<member_id>",
"download_long": null,
"pass_code": "<pass_code>",
"status": "active",
"brand": {
"name": "<name>",
"identifier": "<UUID>"
},
"coupon": {
"title": "<title>",
"identifier": "<UUID>",
"price": 0,
"points": 0,
"external_reference": "<external_reference>"
}
}
]
}
Modify a pass
Modify the expiration date or the status of a pass.
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
passes.get- Type
- string
- Description
The scope required to access this endpoint.
Query parameters
- Name
email- Type
- string|Required
- Description
The email address of the member.
- Name
brand_id- Type
- string|Required
- Description
The UUID of the brand.
- Name
pass_code- Type
- string|Required
- Description
The code of the pass.
- Name
expiration_date- Type
- timestamp|Required
- Description
The new expiration date of the pass.
- Name
status- Type
- string|Required
- Description
The new status of a pass. Can be
['active', 'expired', 'redeemed', 'blocked']
Request
curl -X PUT https://api.walletapp.co/passes
-H "Content-Type: application/json"
-H "Accept: application/json"
-H "Authorization: Bearer <your_token>"
-d '{
"brand_id": "<brand_id>",
"email": "<email>",
"pass_code": "<pass_id||qr_code>",
"expiration_date": "<timestamp>",
"status": "<status>"
}'
Response
{
"success": true,
"message": "Pass updated successfully",
"transaction_id": "<uuid>"
}
Remove a pass
Remove a pass from a member.
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
passes.get- Type
- string
- Description
The scope required to access this endpoint.
Query parameters
- Name
email- Type
- string|Required
- Description
The email address of the member.
- Name
brand_id- Type
- string|Required
- Description
The UUID of the brand.
- Name
pass_code- Type
- string|Required
- Description
The code of the pass.
Request
curl -X DELETE https://api.walletapp.co/passes
-H "Content-Type: application/json"
-H "Accept: application/json"
-H "Authorization: Bearer <your_token>"
-d '{
"brand_id": "<brand_id>",
"email": "<email>",
"pass_code": "<pass_id||qr_code>"
}'
Response
{
"success": true,
"message": "Pass removed"
}
Email a pass
Send a specific coupon pass to a member.
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
passes.get- Type
- string
- Description
The scope required to access this endpoint.
Body parameters
- Name
email- Type
- string|Required
- Description
The email address of the member.
- Name
brand_id- Type
- string|Required
- Description
The UUID of the brand.
- Name
coupon_id- Type
- string|Required
- Description
The id of the coupon to send.
- Name
status- Type
- string|Required
- Description
The status of the pass. Can be:
['active', 'blocked', 'expired', 'redeemed']
- Name
pass_title- Type
- string
- Description
A custom title to display on this pass.
- Name
expiration_date- Type
- string
- Description
The expiration date of the pass.
Request
curl -X POST https://api.walletapp.co/passes
-H "Content-Type: application/json"
-H "Accept: application/json"
-H "Authorization: Bearer <your_token>"
-d '{
'email': '<email>',
'brand_id': '<brand_id>',
'coupon_id': '<coupon_id>',
'expiration_date': '<expiration_date>',
'pass_title': '<pass_title>',
'status': 'active'
}'
Response
{
"success": true,
"message": "Email has been send"
}
Create a pass link
This endpoint generates a new pass and returns the url to the download page.
Required headers
- Name
Authorization- Type
- string
- Description
The token itself as a bearer token.
Abilities
- Name
passes.get- Type
- string
- Description
The scope required to access this endpoint.
Body parameters
- Name
email- Type
- string|Required
- Description
The email address of the member.
- Name
brand_id- Type
- string|Required
- Description
The UUID of the brand.
- Name
coupon_id- Type
- string|Required
- Description
The id of the coupon to send.
- Name
status- Type
- string|Required
- Description
The status of the pass. Can be:
['active', 'blocked', 'expired', 'redeemed']
- Name
pass_title- Type
- string
- Description
A custom title to display on this pass.
- Name
expiration_date- Type
- string
- Description
The expiration date of the pass.
Request
curl -X POST https://api.walletapp.co/passes/url
-H "Content-Type: application/json"
-H "Accept: application/json"
-H "Authorization: Bearer <your_token>"
-d '{
'email': '<email>',
'brand_id': '<brand_id>',
'coupon_id': '<coupon_id>',
'expiration_date': '<expiration_date>',
'pass_title': '<pass_title>',
'status': 'active'
}'
Response
{
"success": true,
"url": "my.walletapp.co/pass/<pass_code>"
}
Modify points/balance
This api allows you to modify the amount of points and/or balance on a storecard. You can update points, balance, or both in a single request.
The endpoint accepts three optional fields that give the platform a richer picture of the transaction: description (what was charged), transaction_reference (idempotency key for safe retries), and vat_rate. Old integrations that don't send them continue to work unchanged — the platform falls back to a price-match heuristic against the organization's product catalogue, then to the terminal's name.
Storecards linked to an external card system (Totalwash / Technodata). For these cards the balance is owned by the external system and synchronised back to Walnut. A positive balance sent to this endpoint only changes Walnut's copy and is overwritten at the next sync — the top-up does not reach the washcard. To top up such a card, use List top-up products and Top up with a product instead.
Required headers
- Name
Authorization- Type
- string
- Description
The scanner bearer token (device token). No specific abilities required.
Body parameters
- Name
points- Type
- numeric|Optional
- Description
A postive or negative number to add or subtract from the current points balance. At least one of
pointsorbalancemust be provided.
- Name
balance- Type
- numeric|Optional
- Description
A postive or negative number to add or subtract from the current monetary balance. At least one of
pointsorbalancemust be provided.
- Name
organization_identifier- Type
- string|Required
- Description
The identifier of the organization.
- Name
pass_code- Type
- string|Required
- Description
The pass code of the pass to modify.
- Name
external_reference- Type
- string|Optional
- Description
The external reference of the transaction. Echoed back as
transaction_idin the response.
- Name
description- Type
- string|Optional
- Description
Human-readable description of what was charged (e.g.
"Latte macchiato","Tier 1 punch"). Persisted to the storecard's transaction history and surfaced on the per-pass view in the dashboard. Maximum 191 characters. When omitted, the platform infers a description by matching the transaction amount against the organization's products, falling back to the terminal's name.
- Name
transaction_reference- Type
- string|Optional
- Description
Idempotency key. If a previous request for the same pass and storecard was POSTed with the same
transaction_reference, this request is treated as a retry: the balance and points are not changed a second time, no extra history row is written, and the response'swas_duplicatefield will betrue. Recommended for any device that retries on transient failures. Maximum 64 characters. When omitted, the platform generates a UUID server-side and returns it in the response so it can be reused for follow-up retries.
- Name
vat_rate- Type
- integer|Optional
- Description
VAT rate that applies to this line, expressed as a whole percentage between
0and21. Stored alongside the transaction for use by the platform's invoicing flows. Defaults apply when omitted.
Product-restricted storecards
Some storecards (typically postpaid / subscription / punch card categories) are configured by the organization to accept only a specific subset of products. When such a card is scanned, the platform tries to resolve the transaction to one of the allowed products using, in order: the request's description, the terminal's configured product, and a price-match against the catalogue.
If the resolved product is on the allowed list, the request proceeds as normal. Otherwise the request is refused with a 422 response — see the Restriction denied example on the right. Top-up scans (a positive balance value) always bypass the restriction.
Request
curl -X POST https://api.walletapp.co/passes/scan/storecard
-H "Content-Type: application/json"
-H "Accept: application/json"
-H "Authorization: Bearer <scanner_device_token>"
-d "{
'points': 1,
'balance': 5.50,
'organization_identifier': '<organization_identifier>',
'pass_code': '<pass_code>',
'external_reference': '<external_reference>',
'description': 'Latte macchiato',
'transaction_reference': '<idempotency_key>',
'vat_rate': 21
}"
Response
{
"success": true,
"message": "Pass updated",
"transaction_id": "<transaction uuid>",
"transaction_reference": "<idempotency_key>",
"was_duplicate": false,
"points": 11,
"balance": 25.50
}
Response - Duplicate retry
{
"success": true,
"message": "Pass updated",
"transaction_id": "<transaction uuid>",
"transaction_reference": "<idempotency_key>",
"was_duplicate": true
}
Error - Restriction denied (product not allowed)
{
"message": "Product \"Latte macchiato\" is not allowed on this storecard. The card is restricted to: Espresso, Cappuccino.",
"error_code": "product_not_allowed",
"allowed_products": ["Espresso", "Cappuccino"],
"resolved_product_id": 42,
"resolved_description": "Latte macchiato"
}
Error - Restriction denied (cannot validate)
{
"message": "This card is restricted to specific products. The device did not provide enough information to validate the scan — set a description or transaction_reference on the request.",
"error_code": "cannot_validate_restriction",
"allowed_products": ["Espresso", "Cappuccino"],
"resolved_product_id": null,
"resolved_description": "Charged at: Counter-01"
}
List top-up products
Some storecards are linked to an external card system that owns the balance — currently Totalwash (Technodata) washcards. Those systems do not accept a free amount: a top-up is always the purchase of one of their top-up products (for example "€25 credit, get €27.50"). This endpoint returns the products available for a pass, so your kiosk or POS can let the customer choose one.
The response's provider tells you which system the card is linked to. Storecards that are not linked to such a system return 422 with error_code: "no_provider" — use Modify points/balance for those.
Requires a scanner device token carrying the storecard.topup ability. Scanner tokens do not have it by default: switch on API top-ups for the scanner on the Scanners page of the dashboard. The device's existing token gets the ability right away — no new token is needed.
Required headers
- Name
Authorization- Type
- string
- Description
The scanner bearer token (device token) with the
storecard.topupability.
Query parameters
- Name
organization_identifier- Type
- string|Required
- Description
The identifier of the organization.
- Name
pass_code- Type
- string|Required
- Description
The pass code (QR value) of the storecard pass. The pass must belong to the brand of the device's location.
Product fields
- Name
productcode- Type
- string
- Description
The code to send to Top up with a product.
- Name
description- Type
- string
- Description
Display name of the product, as configured in the external system.
- Name
price- Type
- numeric
- Description
What the customer pays (in euros). Collecting this payment is up to your integration.
- Name
amount- Type
- numeric
- Description
The credit added to the card. Can be higher than
pricefor bonus products.
Request
curl -G https://api.walletapp.co/passes/storecard/topup-products \
-H "Accept: application/json" \
-H "Authorization: Bearer <scanner_device_token>" \
-d organization_identifier=<organization_identifier> \
-d pass_code=<pass_code>
Response
{
"success": true,
"provider": "totalwash",
"products": [
{ "productcode": "TW25", "description": "Tegoed 25", "price": 25.00, "amount": 27.50 },
{ "productcode": "TW50", "description": "Tegoed 50", "price": 50.00, "amount": 57.50 }
]
}
Error - Not linked to an external system
{
"success": false,
"error_code": "no_provider",
"message": "This storecard is not linked to an external card system that supports product top-ups. Use POST /passes/scan/storecard to change its balance."
}
Top up with a product
Buys one top-up product on the external card system for this pass and mirrors the credit into Walnut: the storecard balance, the transaction history (type topup, with your device as the location) and the customer's Wallet pass are updated. Price and credited amount are always taken from the external system's own product list — you only send the productcode.
Call this after you have collected the customer's payment. The request makes two or three calls to the external system; use a client timeout of at least 15 seconds.
Retries
Send a transaction_reference and reuse it when you retry. A repeat of a top-up that already succeeded returns 200 with was_duplicate: true and does not buy a second product. While the first request is still running, a concurrent retry gets 409 (error_code: "in_progress") — wait and retry. A failed top-up (any non-2xx response) has not credited the card and may be retried with the same reference.
Required headers
- Name
Authorization- Type
- string
- Description
The scanner bearer token (device token) with the
storecard.topupability.
Body parameters
- Name
organization_identifier- Type
- string|Required
- Description
The identifier of the organization.
- Name
pass_code- Type
- string|Required
- Description
The pass code (QR value) of the storecard pass.
- Name
productcode- Type
- string|Required
- Description
A
productcodefrom List top-up products.
- Name
transaction_reference- Type
- string|Optional
- Description
Idempotency key, maximum 64 characters. Strongly recommended.
Error codes
- Name
403 · unauthorized / device_token_required- Type
- error
- Description
The token is not a scanner device token, lacks the
storecard.topupability, or belongs to another organization.
- Name
404 · pass_not_found- Type
- error
- Description
No storecard pass with this code for the device's brand.
- Name
409 · in_progress- Type
- error
- Description
A request with the same
transaction_referenceis still being processed.
- Name
422 · no_provider / no_product / no_card / no_email- Type
- error
- Description
The card is not linked to an external system, the product is not available for this card, or the card/member is incomplete. Blocked or disabled passes are also refused with
422.
- Name
502 · api_error- Type
- error
- Description
The external system refused or failed. Nothing was credited.
Request
curl -X POST https://api.walletapp.co/passes/storecard/topup \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer <scanner_device_token>" \
-d '{
"organization_identifier": "<organization_identifier>",
"pass_code": "<pass_code>",
"productcode": "TW25",
"transaction_reference": "<idempotency_key>"
}'
Response
{
"success": true,
"provider": "totalwash",
"message": "Top-up successful.",
"was_duplicate": false,
"transaction_reference": "<idempotency_key>",
"balance": 37.50,
"previous_balance": 10.00,
"product": { "productcode": "TW25", "description": "Tegoed 25", "price": 25.00, "amount": 27.50 }
}
Response - Duplicate retry
{
"success": true,
"provider": "totalwash",
"message": "Top-up already applied.",
"was_duplicate": true,
"transaction_reference": "<idempotency_key>",
"balance": 37.50
}
Error - Product not available
{
"success": false,
"error_code": "no_product",
"message": "This top-up product is not available for this card."
}
