In this guide, you will be introduced to the features of the zendit catalog and key concepts for integration.
For catalog management from the user console, please see the user guide.
zendit provides catalogs for Mobile Top Up, Digital Gift Cards, Prepaid Utilities and eSIMs.
Products are grouped by the catalog format they use and are separated into endpoints specific to the product type.
zendit offers the following product types for catalogs and purchases through the API:
The following sections will guide you through the key concepts of the zendit catalog.
The zendit catalog is based on the concept of an offer. Each offer has details about the product, the value delivered by the offer, the brand for the offer and pricing that may be set by the integrating partner to expose to their customers.
Offers have one of two price types: FIXED price items or RANGE price items.
FIXED price offers deliver a specific value for the product. The value delivered is predetermined on the catalog. As an example, a $10 value mobile topup for Claro delivering a set amount of GTQ based on the current FX rate for the offer.
RANGE price offers may be fulfilled based on the value (PRICE) to deliver within the boundaries of a minimum value, maximum value and in values within the increment for the offer. As an example, a CLARO range offer in USD is delivered at a price between 7 USD and 100 USD in increments of 1 USD. The offer may only be purchased in whole dollar increments when purchasing by PRICE.
RANGE price offers also support fulfillment by a ZEND value using the local currency.
The Zendit catalog can be filtered by Region and Country to narrow down searching for offers. When searching by Region, the following list of Regions are available:
The Global region is surfaced for products that have worldwide support including some eSIM plans and Digital Gift Cards.
Countries on products use the ISO 3166-1 alpha-2 standard for. 2 letter country codes. For eSIMs, there are some non standard ISO codes such as XK for Kosovo that is recognized by the European Commission and the World Bank Databank but not currently in the ISO standard.
FX rates are subject to change in the catalog for values sent in local currency. While some FX rates are periodic and will retain a value over several days there are many offers that adjust the sent value based on a daily FX. Rates that adjust daily are refreshed and available at 08:30:00 UTC time daily.
When retrieving the catalog all Price, Cost and Send values contain a currency divisor. Numeric values are given in the lowest unit of currency (minor currency unit). For example, values for US Dollar (USD) are in pennies making a value of 1 = $0.01 and a value of 1500 = $15.00. To handle the display of pricing or delivery values, the currency divisor in the structure will assist in converting the currency into a display value.
For example, for XOF the currency divisor will be 1 since XOF doesn’t contain fractional units.
For USD the currency divisor will be 100 since USD supports up to 2 decimal places.
For JOD the currency divisor will be 1000 since JOD supports up the 3 decimal places.
When formatting a currency for display, use the numeric value given and divide it by the currency divisor. The resulting value will be formatted in the number of decimal places that the currency supports.
For example, when formatting 2375 XOF, take 2375 and divide by the currency divisor of 1 to get the resulting 2375 XOF.
When formatting 2375 USD, take 2375 and divide it by the currency divisor of 100 to get the resulting 23.75 USD.
When formatting 2375 JOD, take the 2375 and divide it by the currency divisor of 1000 to get the resulting 2.375 JOD.
When retrieving the catalog, all offers are returned whether they are enabled or disabled. When evaluating a catalog item it is important to check whether the offer is enabled before exposing it to a user. Offers that are disabled in the catalog console will fail if attempted to be fulfilled. This is a security feature that allows catalog managers to turn on offers or turn them off.
Offers may be filtered by their subtype when integrating with the catalog. Subtypes available include:
Mobile Top Up offers send a specific amount of value to the recipient.
Mobile Bundles deliver multiple services for the recipient including set amounts of voice, SMS, or data with some bundles delivering unlimited usage of some apps or unlimited voice or SMS usage. Bundles frequently have a set duration of time the bundle applies to the recipient.
Mobile Data delivers data services to recipients that may include specific amounts of data or unlimited data. Data plans frequently have a set duration of time that the data plan applies to the recipient.
eSIMs fall into 2 main types: Standard providing a fixed amount of data for a specified number of days and Unlimited Plans that provide specific amounts of high speed data per day and unlimited data at throttled speeds beyond. Please see the eSIM guide for more information.
All prepaid utilities use the same subtype Utilities.
Digital Gift Cards have a flexible set of subtypes to categorize them based on the type of business be it Clothing & Accessories, Food & Beverage, etc. These subtypes may be used to categorize your catalog in your integration.
All offers contain a Cost and a Price structure. Products that deliver a local value will also contain a Sent structure. Refer to the API Documentation for more information.
In the zendit catalog, each item has a cost provided in your wallet currency. The values included are:
| Field | Meaning |
| currency | Values in the cost structure will be in the currency specified. This will be the same currency as your zendit wallet. |
| currencyDivisor | The currency divisor is useful for converting the integer values to decimal values as desired. |
| discount | The discount from the full value of the item to your cost for the item. |
| fee | The fixed amount of fee that may be applied to the product. Few products have a fee applied. |
| feePct | A percentage based fee applied to the cost. |
| fixed | The price of a FIXED type offer exclusive of fees. |
| fx | For products that deliver a VALUE (Mobile Top Up, Digital Gift Cards, Prepaid Utilities) the FX rate from the cost to the sent value. |
| min | The minimum price of a RANGE type offer exclusive of fees. |
| max | The maximum price of a RANGE type offer exclusive of fees |
To understand the full cost of an item, use the cost field (fixed for FIXED offers, min/max for RANGE offers) and add the fee amounts to arrive at the full cost of the item.
The Price Structure is useful for partners who don’t have their own catalog system and wish to use zendit’s pricing module for setting the price of items to expose to customers purchasing products. Partners using their own catalog system and selling RANGE type offers will need to understand the values in this structure to submit transactions correctly in zendit. The values in the price structure are as follows:
| Field | Meaning |
| currency | Currency for the price. This will match the wallet currency. |
| currencyDivisor | Divisor useful for converting integer prices to decimal values. |
| fee | Fee setup in the pricing module of the catalog to add to the product price presented to the customer. |
| fixed | Price to charge a customer for a FIXED type product. |
| fx | The FX applied for products that deliver VALUE to the local currency based on the price. |
| increment | The increment amount supported by a RANGE type product. |
| margin | The margin on the product from the PRICE to the COST exclusive of fees. |
| max | The maximum value that may be delivered by a RANGE product. |
| min | The minimum value that may be delivered by a RANGE product. |
| suggestedFixed | zendit’s suggested price for a fixed price product. |
| suggestedFx | zendit’s suggested FX to apply to a range product. |
The Price structure allows the partner to handle pricing for an item to present to the user. For range products, the value must be between the minimum and maximum values and in increments as defined by the increment. For more information on how RANGE offers are handled, see the Transaction Guide.
The Sent structure is provided when a product delivers a value in local currency including Mobile Top Up products (all subtypes), Digital Gift Cards (all subtypes) and Prepaid Utilities.
The sent structure contains the following values:
| Field | Meaning |
| currency | The currency for the local value that is sent. |
| currencyDivisor | Divisor useful for converting integer prices to decimal values. |
| fixed | The value sent for a FIXED type product. |
| fx | Not used in delivery, provided for backward compatibility with older integrations. |
| max | The maximum amount of local value delivered for a RANGE product. |
| min | The minimum amount of local value delivered for a RANGE product. |
Products that send a value in local currency, will provide the values the recipient will receive based on the Price of the product.
The zendit catalog may be used directly in an integration providing the product details and customer facing pricing, may be cached locally by the integration with periodic refreshes (suggested update frequency for caches is 30 minutes) or for partners who have their own catalog systems, you may just use the Offer IDs to map products from your own catalog.
Depending on the caching strategy, all catalog items include an updatedAt timestamp with a date/time in UTC. When retrieving a catalog to update cache, the timestamps can be used to note whether an item has been updated since the last cache retrieval to ignore an update.
When retrieving the catalog from the API or through an SDK, all items will be returned in a paginated manner.
zendit provides multiple catalog endpoints based on the product type.
All Mobile Top Up Products including Mobile Top Up, Mobile Bundle and Mobile Data products are available through the /topups catalog endpoints.
An example of a fixed topup product:
{
"enabled": true,
"offerId": "CLARO_GT_OPEN_0010",
"country": "GT",
"regions": [
"Central America"
],
"brand": "Claro",
"brandName": "Claro",
"productType": "TOPUP",
"subTypes": [
"Mobile Top Up"
],
"notes": "",
"shortNotes": "",
"priceType": "FIXED",
"send": {
"currency": "GTQ",
"currencyDivisor": 100,
"fixed": 7660,
"fx": 1
},
"price": {
"currency": "USD",
"currencyDivisor": 100,
"fixed": 1000,
"suggestedFixed": 1000,
"fx": 7.66,
"suggestedFx": 7.66,
"margin": 0.11,
"fee": 0
},
"cost": {
"currency": "USD",
"currencyDivisor": 100,
"fixed": 890,
"fx": 8.60674157,
"fee": 0,
"feePct": 0,
"discount": 0.11
},
"durationDays": 0,
"dataGB": 0,
"dataUnlimited": false,
"smsNumber": 0,
"smsUnlimited": false,
"voiceMinutes": 0,
"voiceUnlimited": false,
"createdAt": "2025-02-17T05:52:01.419Z",
"updatedAt": "2026-02-16T00:00:00Z"
}
Note the values to present for Mobile Bundle products:
For Mobile Data products, a subset of the bundle properties are used including:
All Digital Gift Card and Prepaid Utility products use the /vouchers endpoints.
An example of a Digital Gift Card:
{
"enabled": true,
"offerId": "ADIDAS_US_001_EGIFT",
"country": "US",
"regions": [
"North America"
],
"brand": "Adidas",
"brandName": "Adidas",
"productType": "VOUCHER",
"subTypes": [
"Clothing & Accessories"
],
"notes": "",
"shortNotes": "",
"priceType": "FIXED",
"send": {
"currency": "USD",
"currencyDivisor": 100,
"fixed": 500,
"fx": 1
},
"price": {
"currency": "USD",
"currencyDivisor": 100,
"fixed": 500,
"suggestedFixed": 500,
"fx": 1,
"suggestedFx": 1,
"margin": 0.044,
"fee": 0
},
"cost": {
"currency": "USD",
"currencyDivisor": 100,
"fixed": 478,
"fx": 1.0460251,
"fee": 0,
"feePct": 0,
"discount": 0.044
},
"requiredFields": [
"recipient.firstName",
"recipient.lastName"
],
"deliveryType": "redemptionURL",
"createdAt": "2025-02-17T05:52:04.212Z",
"updatedAt": "2026-02-16T00:00:00Z"
}
For the /voucher endpoints, it’s important to note the requiredFields structure and delivery types. For more information about working with vouchers, see the Digital Gift Cards and Prepaid Utilities Guide.
eSIM products use the /esim endpoints. An example eSIM:
{
"enabled": true,
"offerId": "ESIM-US-HI-30D-5GB-NOROAM",
"regions": [
"North America"
],
"country": "US",
"brand": "eSIM",
"brandName": "eSIM",
"productType": "ESIM",
"subTypes": [
"Standard"
],
"notes": "eSIM Hawaii (USA) No Roaming",
"shortNotes": "eSIM 30 Days, 5 GB",
"priceType": "FIXED",
"price": {
"currency": "USD",
"currencyDivisor": 100,
"fixed": 925,
"suggestedFixed": 925,
"margin": 0.26162162,
"fee": 100
},
"cost": {
"currency": "USD",
"currencyDivisor": 100,
"fixed": 683,
"fee": 0,
"feePct": 0,
"discount": 0.26162162
},
"durationDays": 30,
"dataGB": 5,
"dataUnlimited": false,
"smsNumber": 0,
"smsUnlimited": false,
"voiceMinutes": 0,
"voiceUnlimited": false,
"dataSpeeds": [],
"roaming": [],
"createdAt": "2025-02-17T05:51:56.256Z",
"updatedAt": "2026-02-16T00:00:00Z"
}
On an eSIM, it’s important to note the dataGB, dataUnlimited, dataSpeeds and roaming sections. For more information on eSIM offers, see the eSIM Guide.
All sample code for the catalog is written in TypeScript for Node.js with Express using the Zendit Node.js SDK. Template engine used in the example using Pug templates (sample template not included.)
import express, { Request, Response } from "express";
import { ZenditApi } from "@zenditplatform/zendit-sdk";
import { DtoESimOffersResponse } from "@zenditplatform/zendit-sdk";
import dotenv from 'dotenv';
// Load config from .env file
dotenv.config();
// Create instance of ZenditApi using the ZENDIT_API_KEY set in the .env file
const zendit = new ZenditApi(process.env.ZENDIT_API_KEY);
export const esimCatalog = express.Router();
// Get the catalog using the header acceptance to return either a page with a PUG template
// or the JSON from the catalog
esimCatalog.get("/catalog/esims", (req: Request, res: Response): void => {
if (req.headers.accept == 'application/json') {
// Render the JSON for the catalog
sendJSON(req, res);
} else {
// Render the page for the catalog
sendPage(req, res);
}
})
// Sends the page version of the catalog using request filters and renders it with PUG templates
function sendPage(req: Request, res: Response) {
// Catalog filters passed from the query sttring
// Set the _limit to the passed value or default to 10 items
const _limit = req.query._limit == null ? 10 : Number(req.query._limit);
// Set the _offset to the passed value or default to 0
const _offset = req.query._offset == null ? 0 : Number(req.query._offset);
// Set the brand to the passed value or default to an empty string (no filter)
const brand = req.query.brand == null ? "" : String(req.query.brand);
// Set the destination country to the passed value or default to an empty string (no filter)
const country = req.query.country == null ? "" : String(req.query.country);
// Set the subType to the passed value or default to an empty string (no filter)
const subType = req.query.subType == null ? "" : String(req.query.subType);
//Set the region to the passed value or default to an empty string (no filter)
const region = req.query.regions == null ? "" : String(req.query.regions);
// Get the list of eSIM offers matching the filters and then render it as JSON
zendit.esimOffersGet(_limit, _offset, brand, country, region, subType)
.then(function (catalog) {
// Render the catalog with a template
res.render("catalog/esim", {catalog: catalog});
})
.catch(error => res.send("Error getting catalog from Zendit."));
}
// Sends the JSON version of the catalog using request flters and renders it in JSON
function sendJSON(req: Request, res: Response) {
// Catalog filters passed from the query sttring
// Set the _limit to the passed value or default to 10 items
const _limit = req.query._limit == null ? 10 : Number(req.query._limit);
// Set the _offset to the passed value or default to 0
const _offset = req.query._offset == null ? 0 : Number(req.query._offset);
// Set the brand to the passed value or default to an empty string (no filter)
const brand = req.query.brand == null ? "" : String(req.query.brand);
// Set the destination country to the passed value or default to an empty string (no filter)
const country = req.query.country == null ? "" : String(req.query.country);
// Set the subType to the passed value or default to an empty string (no filter)
const subType = req.query.subType == null ? "" : String(req.query.subType);
//Set the region to the passed value or default to an empty string (no filter)
const region = req.query.regions == null ? "" : String(req.query.regions);
// Get the list of eSIM offers matching the filters and then render it as JSON
zendit.esimOffersGet(_limit, _offset, brand, country, region, subType)
.then(function (catalog) {
res.json(catalog);
})
.catch(error => res.send("Error getting catalog from Zendit."));
}