VIO External API Reference
This document provides a comprehensive reference for integrating with the VIO External API.
Table of Contents
1. Overview
What is the VIO External API?
The VIO External API allows tenants to integrate their systems with the VIO platform programmatically. Use this API to:
- Manage vouchers (create, update, delete, redeem)
- Manage users and their data
- Create and manage voucher campaigns
- Handle tokens, balances, and transactions
- Access analytics and reporting data
Base URL
https://{your-domain}/api/external/v1All API endpoints are relative to this base URL.
API Versioning
The current API release version is 1.1.0. The URL path version remains v1. When breaking changes are introduced, a new URL path version will be released while maintaining backwards compatibility for existing versions.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Your Application │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. Include X-API-Key header │ │
│ │ 2. Make HTTPS request to /api/external/v1/* │ │
│ │ 3. Parse JSON response │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────┐
│ VIO API Server │
│ /api/external/v1 │
│ │
│ • Authentication │
│ • Rate Limiting │
│ • Scope Validation │
│ • Request Handler │
└─────────────────────┘
│
▼
┌─────────────────────┐
│ Database │
│ (Your Tenant Data) │
└─────────────────────┘2. Authentication
API Keys
All API requests require authentication using an API key. API keys are scoped to your tenant and can have different permission levels.
Obtaining an API Key
- Log in to the VIO Admin Portal
- Navigate to Settings > API Keys
- Click Create API Key
- Select the scopes (permissions) for the key
- Optionally configure IP whitelisting
- Copy and securely store the generated key
Important: API keys are shown only once upon creation. Store them securely.
Using Your API Key
Include your API key in the X-API-Key header with every request:
curl -X GET "https://your-domain.com/api/external/v1/vouchers" \
-H "X-API-Key: vio_live_your_api_key_here"API Key Format
API keys follow this format:
- Live keys:
vio_live_xxxxxxxxxxxxxxxx - Test keys:
vio_test_xxxxxxxxxxxxxxxx
API Key Scopes
Each API key can be assigned one or more scopes that determine which endpoints it can access:
| Scope | Description | Endpoints |
|---|---|---|
vouchers | Manage vouchers and claims | /vouchers/* |
users | Manage users | /users/* |
campaigns | Manage campaigns | /campaigns/* |
tokens | Manage tokens and balances | /tokens/* |
analytics | Access analytics data | /analytics/* |
IP Whitelisting
For additional security, you can restrict API key usage to specific IP addresses:
- Go to Admin Portal > Settings > API Keys
- Edit your API key
- Add allowed IP addresses or CIDR ranges
- Save changes
Requests from non-whitelisted IPs will receive a 403 Forbidden response.
3. Rate Limiting
Default Limits
To ensure fair usage and platform stability, the API enforces rate limits:
| Limit Type | Default Value |
|---|---|
| Per Minute | 60 requests |
| Per Day | 10,000 requests |
Rate Limit Headers
Every response includes rate limit information in the headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp when the rate limit resets |
Example Response Headers
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1699574400Rate Limit Exceeded
When you exceed the rate limit, you'll receive a 429 Too Many Requests response:
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please retry after 60 seconds."
}
}The response includes a Retry-After header indicating how long to wait before retrying.
Best Practices
- Implement exponential backoff when receiving
429responses - Cache responses when appropriate
- Batch operations when possible
- Monitor your usage via the rate limit headers
4. Request & Response Format
Request Format
- All request bodies must be JSON
- Include
Content-Type: application/jsonheader for POST/PATCH requests - Query parameters are used for filtering and pagination
Success Response
Successful responses follow this structure:
{
"success": true,
"data": { ... },
"message": "Optional success message"
}Paginated Response
List endpoints return paginated data:
{
"success": true,
"data": [ ... ],
"pagination": {
"page": 1,
"limit": 20,
"total": 150,
"totalPages": 8,
"hasNextPage": true,
"hasPrevPage": false
}
}Pagination Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number (1-indexed) |
limit | integer | 20 | Items per page (max: 100) |
Error Response
Error responses follow this structure:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description"
}
}5. Error Handling
HTTP Status Codes
| Status Code | Meaning |
|---|---|
200 OK | Request succeeded |
201 Created | Resource created successfully |
400 Bad Request | Invalid request parameters or body |
401 Unauthorized | Missing or invalid API key |
403 Forbidden | API key lacks required scope or IP not whitelisted |
404 Not Found | Resource not found |
429 Too Many Requests | Rate limit exceeded |
500 Internal Server Error | Server error |
Error Codes
| Error Code | Description |
|---|---|
VALIDATION_ERROR | Request body or parameters failed validation |
UNAUTHORIZED | API key is missing or invalid |
FORBIDDEN | API key does not have required scope |
NOT_FOUND | Requested resource does not exist |
RATE_LIMIT_EXCEEDED | Too many requests |
ALREADY_EXISTS | Resource already exists (e.g., duplicate user) |
INSUFFICIENT_BALANCE | Not enough token balance for operation |
ALREADY_REDEEMED | Voucher has already been redeemed |
EXPIRED | Voucher or token has expired |
INTERNAL_ERROR | Unexpected server error |
Example Error Response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request parameters",
"details": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
}6. API Reference: Vouchers
Required Scope: vouchers
Claim & Redeem Flows (Important)
Treat these as two different objects:
- Voucher template (
Voucher): the voucher definition your tenant creates or syncs. - Voucher claim (
UserVoucher): the per-user voucher instance created by claim/send.
Voucher lifecycle by scenario
Use the following flow selection when integrating only through APIs:
| Scenario | Step 1 (issue/claim) | Step 2 (redeem) | When to use |
|---|---|---|---|
| User self-claim from campaign | POST /api/vouchers/:voucherId/claim with campaignId | POST /api/vouchers/me/:userVoucherId/redeem | End-user app (JWT) |
| Tenant/backend directly issues to user | POST /vouchers/:voucherId/send with userId | POST /vouchers/redeem-by-code or POST /vouchers/redeem/:code/pin | API Key server-to-server integration |
The flow is the same for both tenant-managed and external vouchers: claim/send first, then redeem. The difference is what happens at redeem time:
- Tenant-managed voucher: redeem marks the claim as used; no external action is triggered. For
consumptionTypeqr_codeorcoupon_codewith a supplier code pool (CSV uploaded in Admin Portal), redeem assigns the next available code toexternalVoucherCodeand then marks the claim used — only then should end-user apps show that supplier code / generated QR or barcode. - External voucher: redeem triggers a provider purchase. After redeem, check
externalFulfillmentStatusin the response to confirm success.
POST /vouchers/:voucherId/send is the External API equivalent of "claim for a specific user". It creates a UserVoucher and returns a redemptionCode.
Voucher type decision rules
Determine voucher source/type from voucher template fields:
externalProviderandexternalIdboth present: this is an external voucher.externalProviderandexternalIdboth missing: this is a tenant-managed voucher.consumptionTypeis usage mode (vio_code,coupon_code,url,qr_code,manual,zhichong), not source-of-truth for tenant/external.voucherTypeis business category, not source-of-truth for tenant/external.
Where to read decision fields
| What you need to decide | Read from API | Key fields |
|---|---|---|
| Is this voucher external or tenant-managed? | GET /vouchers or GET /vouchers/:voucherId | externalProvider, externalId |
| Which redeem input style is needed? | GET /vouchers or GET /vouchers/:voucherId | consumptionType, externalRequiresDirectOrderParams |
| Which claim instance should be redeemed? | Claim/send response or user-claim list | userVoucherId (_id), redemptionCode, status |
| External fulfillment outputs after redeem | Redeem response | externalVoucherCode, externalRedemptionUrl, externalOrderId, externalFulfillmentStatus |
GET /vouchers/redeem/:code/info is display-oriented and should not be used as the voucher type decision source.
Claim/Redeem Request & Response Examples
A) Directly issue (send) a voucher to a user via External API (API Key)
This is the correct endpoint when your backend issues vouchers directly to users.
curl -X POST "https://your-domain.com/api/external/v1/vouchers/{voucherId}/send" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"userId": "507f1f77bcf86cd799439015"
}'Example success response (key fields):
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439050",
"voucherId": "507f1f77bcf86cd799439011",
"userId": "507f1f77bcf86cd799439015",
"status": "active",
"redemptionCode": "VCH-M1ABC2-XY3Z"
},
"message": "Voucher sent to user"
}B) Redeem by redemption code via External API (API Key)
Suitable for non-zhichong server-to-server/store integrations.
curl -X POST "https://your-domain.com/api/external/v1/vouchers/redeem-by-code" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"redemptionCode": "VCH-M1ABC2-XY3Z",
"location": "Store #42",
"notes": "POS order #12345"
}'Example success response (key fields):
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439050",
"status": "redeemed",
"redemptionCode": "VCH-M1ABC2-XY3Z",
"redeemedAt": "2024-02-01T10:15:00.000Z",
"externalVoucherCode": "ABC-123-XYZ",
"externalRedemptionUrl": null,
"externalOrderId": "69f75fe31134f32e472b2f98",
"externalFulfillmentStatus": "fulfilled"
},
"message": "Voucher redeemed"
}C) zhichong direct recharge redeem (Member API, JWT)
When consumptionType is zhichong, redeem through member endpoint and include recharge account:
curl -X POST "https://your-domain.com/api/vouchers/me/{userVoucherId}/redeem" \
-H "Authorization: Bearer {member_jwt}" \
-H "Content-Type: application/json" \
-d '{
"providerParams": {
"account": "12312332123"
}
}'Example success response (key fields):
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439050",
"status": "redeemed",
"externalFulfillmentStatus": "fulfilled",
"externalIsDirectRecharge": true,
"externalRechargeAccount": "12312332123",
"externalOrderId": "69f75fe31134f32e472b2f98"
},
"message": "Voucher redeemed"
}Voucher Source & Type Matrix
| Voucher source | externalProvider | externalId | Typical consumptionType | Recommended claim/send | Recommended redeem |
|---|---|---|---|---|---|
| Tenant-managed voucher | missing | missing | vio_code, coupon_code, url, qr_code, manual | POST /vouchers/:voucherId/send (API Key) or member claim flow | POST /vouchers/redeem-by-code, POST /vouchers/redeem/:code/pin, or member redeem |
| External voucher (code/url style) | present | present | usually coupon_code or url | Claim/send first | Redeem after claim/send |
External voucher (zhichong) | present | present | zhichong | Claim/send first | POST /api/vouchers/me/:userVoucherId/redeem with providerParams.account |
POST /api/external/v1/vouchers/redeem-by-codedoes not acceptproviderParams.POST /api/external/v1/vouchers/redeem/:code/pincannot be used forzhichong.
Error handling & fulfillment status
Common claim/send errors
| HTTP Status | Error Code | Cause |
|---|---|---|
| 404 | NOT_FOUND | voucherId or userId does not exist |
| 400 | VALIDATION_ERROR | Voucher is not active or has expired |
| 400 | VALIDATION_ERROR | Voucher is sold out (claimedQuantity >= totalQuantity) |
| 400 | VALIDATION_ERROR | User has reached maxClaimsPerUser limit |
Common redeem errors
| HTTP Status | Error Code | Cause |
|---|---|---|
| 404 | NOT_FOUND | redemptionCode does not match any active claim |
| 400 | VALIDATION_ERROR | Voucher claim is already redeemed (ALREADY_REDEEMED) |
| 400 | VALIDATION_ERROR | Voucher claim has expired (EXPIRED) |
| 400 | VALIDATION_ERROR | zhichong voucher cannot use PIN redeem |
External voucher fulfillment status
After redeeming an external voucher, inspect externalFulfillmentStatus in the response:
externalFulfillmentStatus | Meaning | Action required |
|---|---|---|
fulfilled | Provider purchase succeeded. externalVoucherCode or externalRedemptionUrl is available (for non-zhichong) | Deliver the code/url to the user |
failed | Provider purchase failed | You may retry by calling redeem again on the same claim. Check externalFulfillmentError for details |
processing | Provider purchase is in progress (rare, async scenarios) | Retry later or poll the claim status via GET /users/:userId/vouchers |
If the provider purchase fails, the redeem request returns an error and the claim remains retryable. The claim record is updated with externalFulfillmentStatus: "failed" and externalFulfillmentError.
Example claim state after a failed external fulfillment:
{
"_id": "507f1f77bcf86cd799439050",
"status": "active",
"externalFulfillmentStatus": "failed",
"externalFulfillmentError": "Provider returned: insufficient inventory",
"externalOrderId": null
}When externalFulfillmentStatus is failed, the claim remains in active status and can be retried by calling redeem again.
List Vouchers
Retrieve a paginated list of vouchers visible to your tenant. You can optionally exclude vouchers manually created by the current tenant.
GET /vouchersQuery Parameters:
| Parameter | Type | Required | Constraints | Description |
|---|---|---|---|---|
page | integer | No | Min: 1. Default: 1 | Page number (1-indexed) |
limit | integer | No | Min: 1, Max: 100. Default: 20 | Items per page |
visibility | string | No | Enum: private, public, shared | Filter by visibility scope |
isActive | boolean | No | String "true" or "false" (parsed as boolean) | Filter by active status |
category | string | No | Free-form string | Filter by category tag |
search | string | No | Free-form string | Search by voucher name (partial match) |
subCompanyId | string | No | MongoDB ObjectId (24 hex chars) | Filter by sub-company |
excludeTenantManualCreated | boolean | No | String "true" or "false". Default: false | When true, exclude vouchers manually created by the current tenant while keeping externally supplied vouchers such as vouchain |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/vouchers?page=1&limit=10&isActive=true&excludeTenantManualCreated=true" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439011",
"name": "20% Off Discount",
"description": "Get 20% off your next purchase",
"value": 20,
"valueType": "percentage",
"terms": "Valid on orders over $50",
"images": ["https://example.com/image.jpg"],
"isActive": true,
"totalQuantity": 100,
"claimedQuantity": 45,
"maxClaimsPerUser": 1,
"voucherType": "discount",
"consumptionType": "coupon_code",
"externalProvider": "vouchain",
"externalId": "VCH-EXT-001",
"externalRequiresDirectOrderParams": false,
"category": "Lifestyle & Services",
"categories": ["food", "lifestyle"],
"settlementAmount": 80,
"settlementCurrency": "THB",
"startDate": "2024-01-01T00:00:00.000Z",
"endDate": "2024-12-31T23:59:59.000Z",
"visibility": "public",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 45,
"totalPages": 5,
"hasNextPage": true,
"hasPrevPage": false
}
}Response Fields (each item in data array):
| Field | Type | Nullable | Constraints / Format | Description |
|---|---|---|---|---|
_id | string | No | MongoDB ObjectId (24 hex chars) | Unique identifier for the voucher |
name | string | No | Min: 1 char. Trimmed | Display name of the voucher |
description | string | No | May be empty string "" | Detailed description of the voucher offer |
value | number | No | >= 0. Default: 0 | Discount value. Interpretation depends on valueType |
valueType | string | No | Enum: fixed, percentage | How value is applied: fixed (flat amount) or percentage (discount rate) |
valueCurrency | string | No | ISO 4217 code (e.g., THB, HKD, USD). Default: THB | Currency for value when valueType is fixed |
minSpend | number | No | >= 0. Default: 0 | Minimum spend required before the voucher can be applied. 0 means no minimum |
maxDiscount | number | Yes | > 0; may be null when unset | Maximum discount cap for percentage vouchers |
terms | string | No | May be empty string "" | Terms and conditions for using the voucher |
images | string[] | No | Array of valid URLs. May be empty [] | Image URLs. First image is the primary display |
isActive | boolean | No | true or false | Whether the voucher is currently active and available for claiming |
isTransferable | boolean | No | true or false. Default: false | Whether a claimed voucher can be transferred between users |
totalQuantity | integer | No | -1 = unlimited; >= 0 = limited | Maximum vouchers available. No more claims when claimedQuantity >= totalQuantity |
claimedQuantity | integer | No | >= 0 | Number of vouchers already claimed by users |
maxClaimsPerUser | integer | No | 0 = unlimited; >= 1 = limited | Maximum times a single user can claim this voucher |
voucherType | string | No | Enum: cash, discount, product, cash_discount. Default: discount | Voucher business category. Do not use for tenant/external source decision |
consumptionType | string | No | Enum: vio_code, coupon_code, url, qr_code, manual, zhichong | Voucher consumption mode. Use with source fields to choose redeem input style |
externalProvider | string | Yes | Provider code or null | External source indicator. Together with externalId determines external voucher |
externalId | string | Yes | Provider-side template ID or null | External source indicator. Together with externalProvider determines external voucher |
externalRequiresDirectOrderParams | boolean | Yes | true / false / null | Whether external redeem may require account params (for example direct recharge flow) |
externalData | object | Yes | Object or null | Raw or extended external provider data. Shape may vary by provider |
category | string | Yes | Enum: Wellness, Health, Food & Beverage, Leisure & Entertainment, Travel & Hospitality, Lifestyle & Services, Others. May be null | Primary product/service category of the voucher. Use this for category-based grouping or filtering |
categories | string[] | No | Array of trimmed strings. May be [] | Legacy free-form category tags (deprecated — prefer category). Still returned for backwards compatibility |
applicableBrands | object[] | No | Each item includes _id, name, slug | Applicable brands/sub-companies. Legacy field; prefer applicableBrandTags |
applicableBrandTags | object[] | No | Each item includes _id, name, logo | Applicable brand tags |
settlementAmount | number | No | >= 0. Default: 0 | Settlement price (per voucher) that VIO accounts to the issuing tenant when the voucher is redeemed in a cross-tenant scenario. This is the wholesale/settlement value the tenant receives — useful for partner billing reconciliation |
settlementCurrency | string | No | ISO 4217 code, uppercase. Default: THB | Currency for settlementAmount |
crossTenantReceivableTiming | string | No | Enum: redemption, consumption. Default: redemption | When to record cross-tenant receivable settlement |
startDate | string | Yes | ISO 8601 datetime. null if not set | When the voucher becomes valid |
endDate | string | Yes | ISO 8601 datetime. null = no expiry | When the voucher expires |
visibility | string | No | Enum: private, public, shared | Access scope. Also controls whether the voucher can be transferred between users |
tenantId | object | No | Includes _id, name, slug | Tenant that owns the voucher |
subCompanyId | object | Yes | Includes _id, name, slug; may be null | Sub-company that owns the voucher |
isOwn | boolean | No | true or false | Whether the voucher belongs to the current request context organization |
ownerOrg | object | Yes | { "name": string, "type": "tenant/subCompany" }; usually omitted for own vouchers | Owner organization info for non-own vouchers |
targetTiers | string[] | No | Array of strings. May be [] | Membership tiers that can see or claim this voucher |
targetUserGroups | string[] | No | Array of MongoDB ObjectIds. May be [] | User groups that can see or claim this voucher |
applicableScope | string | No | Enum: all_outlets, partial_outlets, single_store. Default: all_outlets | Store applicability scope |
applicableCountry | string | Yes | ISO country/region code. May be null | Single applicable country/region. Legacy field |
applicableCountries | string[] | No | Array of ISO country/region codes. May be [] | Applicable countries/regions |
storeId | string | Yes | MongoDB ObjectId. May be null | Store ID for single-store applicability |
storeLocation | object | Yes | Includes name, address, latitude, longitude; may be null | Embedded store location |
bookingEnabled | boolean | No | true or false. Default: false | Whether booking is enabled |
bookingDaysInAdvance | integer | No | >= 0. Default: 0 | How many days in advance users can book |
consumptionMessage | string | No | May be empty string "" | Instructions for manual consumption type |
consumptionUrl | string | No | May be empty string "" | URL used for url consumption type |
consumptionQrCode | string | No | May be empty string "" | QR code image path for qr_code consumption type |
contractAddress | string | Yes | Blockchain contract address. May be null | Voucher NFT contract address |
createdBy | string | Yes | MongoDB ObjectId. May be null | Admin user ID that created/deployed the voucher |
metadata | object | No | Object. Default: {} | Extended metadata |
createdAt | string | No | ISO 8601 datetime | When the voucher was created |
updatedAt | string | No | ISO 8601 datetime | When the voucher was last modified |
Create Voucher
Create a new voucher for your tenant.
POST /vouchersRequest Body:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
name | string | Yes | Min: 1 character. Trimmed | Voucher display name shown to users in the member app |
description | string | No | No max length. Default: "" | Detailed description of the voucher offer. Plain text only |
voucherType | string | No | Enum: cash, discount, product, cash_discount. Default: discount | Business category of the voucher |
visibility | string | No | Enum: private, public, shared. Default: private | Access scope: private (own tenant only), public (all tenants), shared (specific tenants in sharedWithTenants) |
sharedWithTenants | string[] | No | Each element: MongoDB ObjectId (24 hex chars) | Tenant IDs to share with. Only applicable when visibility is shared |
sharedWithUsers | string[] | No | Each element: MongoDB ObjectId (24 hex chars) | User IDs who can see this voucher. Used for targeted distribution |
terms | string | No | No max length. Default: "" | Terms and conditions displayed to users before claim/redeem |
value | number | No | Min: 0. Default: 0 | Discount value. When valueType is fixed, this is the flat discount amount. When percentage, this is the rate (e.g., 20 = 20% off) |
valueType | string | No | Enum: fixed, percentage. Default: fixed | How value is interpreted: fixed (flat currency amount) or percentage (discount rate) |
valueCurrency | string | No | ISO 4217 code (e.g., THB, HKD, USD). Default: THB | Currency code for value when valueType is fixed |
minSpend | number | No | Min: 0. Default: 0 | Minimum purchase amount required before voucher can be applied. 0 = no minimum |
maxDiscount | number | No | Must be > 0 (positive only) | Maximum discount cap. Useful for percentage vouchers (e.g., 20% off but max $100 discount) |
totalQuantity | integer | No | Integer only. -1 = unlimited. Default: -1 | Total vouchers available for claiming. No more claims when fully claimed |
startDate | string | No | ISO 8601 datetime (e.g., 2024-06-01T00:00:00.000Z). Default: current time | When the voucher becomes claimable. Omit for immediately available |
endDate | string | No | ISO 8601 datetime. Must be after startDate | When the voucher expires. Claims/redemptions after this are rejected. Omit for no expiry |
maxClaimsPerUser | integer | No | Integer only. Min: 0. 0 = unlimited. Default: 1 | Maximum times a single user can claim this voucher |
settlementAmount | number | No | Min: 0. Default: 0 | Fiat amount for inter-tenant settlement when redeemed in cross-tenant scenario |
settlementCurrency | string | No | ISO 4217 code. Stored uppercase. Trimmed. Default: THB | Currency for settlementAmount. Defaults to tenant's primary currency from Billing Settings. All settlement records stored in this currency |
category | string | No | Enum: Wellness, Health, Food & Beverage, Leisure & Entertainment, Travel & Hospitality, Lifestyle & Services, Others | Primary voucher category. Prefer this field for categorization, for example "Others" |
categories | string[] | No | Array of strings. Each element trimmed | Category tags for organizing vouchers (e.g., ["food", "lifestyle"]) |
images | string[] | No | Each element must be a valid URL | Image URLs. First image is used as primary display |
targetTiers | string[] | No | Array of strings. Each element trimmed | Membership tier names. Only users in these tiers can see/claim |
targetUserGroups | string[] | No | Each element: MongoDB ObjectId (24 hex chars) | User group IDs. Only users in these groups can see/claim |
subCompanyId | string | No | MongoDB ObjectId (24 hex chars) | Sub-company to scope this voucher to |
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/vouchers" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Sale 20% Off",
"description": "Valid for summer collection",
"value": 20,
"valueType": "percentage",
"category": "Others",
"totalQuantity": 100,
"maxClaimsPerUser": 1,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-08-31T23:59:59.000Z",
"visibility": "public",
"terms": "Cannot be combined with other offers"
}'Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439012",
"name": "Summer Sale 20% Off",
"description": "Valid for summer collection",
"value": 20,
"valueType": "percentage",
"totalQuantity": 100,
"claimedQuantity": 0,
"maxClaimsPerUser": 1,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-08-31T23:59:59.000Z",
"visibility": "public",
"isActive": true,
"createdAt": "2024-05-15T10:00:00.000Z"
},
"message": "Voucher created"
}Response Fields:
Returns the created voucher object. Most fields match List Vouchers Response Fields, but list-only computed fields such as isOwn and ownerOrg are not added by this create endpoint.
Get Voucher Details
Retrieve details of a specific voucher.
GET /vouchers/:voucherIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
voucherId | string | Yes | Voucher ID |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/vouchers/507f1f77bcf86cd799439011" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439011",
"name": "20% Off Discount",
"description": "Get 20% off your next purchase",
"value": 20,
"valueType": "percentage",
"terms": "Valid on orders over $50",
"images": ["https://example.com/image.jpg"],
"isActive": true,
"totalQuantity": 100,
"claimedQuantity": 45,
"maxClaimsPerUser": 1,
"startDate": "2024-01-01T00:00:00.000Z",
"endDate": "2024-12-31T23:59:59.000Z",
"visibility": "public",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
}Response Fields:
Returns the full voucher object. Most fields match List Vouchers Response Fields, but list-only computed fields such as isOwn and ownerOrg are not added by this detail endpoint. For shared vouchers, sharedWithTenants, sharedWithSubCompanies, and sharedWithUsers may be populated with summary objects.
Update Voucher
Update an existing voucher.
PATCH /vouchers/:voucherIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
voucherId | string | Yes | Voucher ID |
Request Body:
All fields are optional. Include only the fields you want to update.
| Field | Type | Constraints | Description |
|---|---|---|---|
name | string | Min: 1 character | Voucher display name |
description | string | No max length | Voucher description |
voucherType | string | Enum: cash, discount, product, cash_discount | Business category of the voucher |
visibility | string | Enum: private, public, shared | Access scope |
sharedWithTenants | string[] | Each element: MongoDB ObjectId (24 hex chars) | Tenant IDs to share with (only for shared visibility) |
sharedWithUsers | string[] | Each element: MongoDB ObjectId (24 hex chars) | User IDs for targeted distribution |
terms | string | No max length | Terms and conditions |
value | number | Min: 0 | Discount value |
valueType | string | Enum: fixed, percentage | How value is interpreted |
valueCurrency | string | ISO 4217 code (e.g., THB, HKD, USD) | Currency for fixed-value vouchers |
minSpend | number | Min: 0 | Minimum purchase amount. 0 = no minimum |
maxDiscount | number | Must be > 0 (positive only) | Maximum discount cap for percentage vouchers |
totalQuantity | integer | Integer only. -1 = unlimited | Total available quantity |
startDate | string | ISO 8601 datetime | When voucher becomes valid |
endDate | string | ISO 8601 datetime | When voucher expires |
maxClaimsPerUser | integer | Integer only. Min: 0. 0 = unlimited | Max claims per user |
settlementAmount | number | Min: 0 | Settlement amount for cross-tenant reconciliation |
settlementCurrency | string | ISO 4217 code. Stored uppercase. Trimmed | Settlement currency. Defaults to tenant's primary currency. All records stored in tenant's currency |
category | string | Enum: Wellness, Health, Food & Beverage, Leisure & Entertainment, Travel & Hospitality, Lifestyle & Services, Others | Primary voucher category, for example "Others" |
isActive | boolean | true or false | Active status |
categories | string[] | Array of strings. Each element trimmed | Category tags |
images | string[] | Each element must be a valid URL | Image URLs |
targetTiers | string[] | Array of strings. Each element trimmed | Membership tier names for targeting |
targetUserGroups | string[] | Each element: MongoDB ObjectId (24 hex chars) | User group IDs for targeting |
Example Request:
curl -X PATCH "https://your-domain.com/api/external/v1/vouchers/507f1f77bcf86cd799439011" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Voucher Name",
"isActive": false
}'Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439011",
"name": "Updated Voucher Name",
"isActive": false,
"updatedAt": "2024-02-01T12:00:00.000Z"
},
"message": "Voucher updated"
}Delete Voucher
Soft delete a voucher (marks as deleted, not permanently removed).
DELETE /vouchers/:voucherIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
voucherId | string | Yes | Voucher ID |
Example Request:
curl -X DELETE "https://your-domain.com/api/external/v1/vouchers/507f1f77bcf86cd799439011" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439011",
"isDeleted": true,
"isActive": false,
"deletedAt": "2024-02-01T12:00:00.000Z"
},
"message": "Voucher deleted"
}Response Fields:
Returns the updated voucher object after soft delete. Key fields are _id, isDeleted: true, isActive: false, and deletedAt; other voucher fields may also be present.
Duplicate Voucher
Create a copy of an existing voucher.
POST /vouchers/:voucherId/duplicatePath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
voucherId | string | Yes | Voucher ID to duplicate |
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/vouchers/507f1f77bcf86cd799439011/duplicate" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439013",
"name": "20% Off Discount (Copy)",
"description": "Get 20% off your next purchase",
"value": 20,
"valueType": "percentage",
"claimedQuantity": 0,
"createdAt": "2024-02-01T12:00:00.000Z"
},
"message": "Voucher duplicated"
}Response Fields:
Returns the duplicated voucher object. The copy uses the original fields, appends (Copy) to name, resets claimedQuantity to 0, and receives a newly deployed contractAddress.
Send Voucher to User
Send (issue) a voucher directly to a user. This mints an NFT and creates an active voucher claim for the specified user, bypassing the campaign claim flow.
POST /vouchers/:voucherId/sendPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
voucherId | string | Yes | Voucher ID to send |
Request Body:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
userId | string | Yes | Min: 1 character. MongoDB ObjectId (24 hex chars) | User ID to send the voucher to |
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/vouchers/507f1f77bcf86cd799439011/send" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"userId": "507f1f77bcf86cd799439015"
}'Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439050",
"userId": "507f1f77bcf86cd799439015",
"voucherId": "507f1f77bcf86cd799439011",
"tenantId": "507f1f77bcf86cd799439001",
"nftTokenId": "42",
"status": "active",
"redemptionCode": "VCH-M1ABC2-XY3Z",
"expiresAt": "2024-08-31T23:59:59.000Z",
"settlementAmount": 0,
"settlementCurrency": "HKD",
"claimedAt": "2024-02-01T12:00:00.000Z",
"createdAt": "2024-02-01T12:00:00.000Z"
},
"message": "Voucher sent to user"
}Response Fields:
| Field | Type | Nullable | Constraints / Format | Description |
|---|---|---|---|---|
_id | string | No | MongoDB ObjectId (24 hex chars) | Unique identifier for this voucher claim (UserVoucher record) |
userId | string | No | MongoDB ObjectId (24 hex chars) | The user who received the voucher |
voucherId | string | No | MongoDB ObjectId (24 hex chars) | The voucher template ID that was sent |
tenantId | string | No | MongoDB ObjectId (24 hex chars) | The tenant that owns this voucher claim |
nftTokenId | string | Yes | Numeric string (blockchain token ID). null if minting is pending | On-chain NFT token ID minted for this claim |
status | string | No | Enum: active, claimed, redeemed, expired. Always active for newly sent | Current claim status |
redemptionCode | string | No | Format: VCH-{base36_timestamp}-{4_random_chars} (e.g., VCH-M1ABC2-XY3Z). Unique | Code the user presents to redeem the voucher |
expiresAt | string | Yes | ISO 8601 datetime. null = no expiry | When this claim expires. Inherited from voucher's endDate at claim time |
settlementAmount | number | No | >= 0. Default: 0 | Settlement amount copied from voucher at claim time for cross-tenant reconciliation |
settlementCurrency | string | No | ISO 4217 code, uppercase (e.g., HKD, THB). Default: THB | Currency for settlementAmount |
claimedAt | string | No | ISO 8601 datetime | When the voucher was sent to the user |
createdAt | string | No | ISO 8601 datetime | Record creation timestamp |
Error Responses:
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Voucher is not active, expired, sold out, or user at claim limit |
| 404 | NOT_FOUND | Voucher or user not found |
List Voucher Claims
Get a list of all voucher claims for your tenant.
GET /vouchers/claims/listQuery Parameters:
| Parameter | Type | Required | Constraints | Description |
|---|---|---|---|---|
page | string | No | Parsed as integer. Min: 1. Default: 1 | Page number (1-indexed) |
limit | string | No | Parsed as integer. Min: 1. Default: 20 | Items per page |
voucherId | string | No | MongoDB ObjectId (24 hex chars) | Filter by voucher ID |
status | string | No | Enum: active, claimed, redeemed, expired | Filter by claim status |
fromDate | string | No | ISO 8601 datetime (e.g., 2024-01-01T00:00:00.000Z) | Filter claims from this date (inclusive) |
toDate | string | No | ISO 8601 datetime | Filter claims until this date (inclusive) |
search | string | No | Free-form string | Search by user name or email (partial match) |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/vouchers/claims/list?status=active&limit=10" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439014",
"voucherId": {
"_id": "507f1f77bcf86cd799439011",
"name": "20% Off Discount",
"images": ["https://example.com/image.jpg"],
"value": 20,
"valueType": "percentage"
},
"userId": {
"_id": "507f1f77bcf86cd799439015",
"displayName": "John Doe",
"email": "john@example.com",
"avatar": "https://example.com/avatar.jpg"
},
"tenantId": "507f1f77bcf86cd799439001",
"status": "active",
"redemptionCode": "VCH-M1ABC2-XY3Z",
"claimedAt": "2024-01-15T14:30:00.000Z",
"expiresAt": "2024-12-31T23:59:59.000Z",
"settlementAmount": 80,
"settlementCurrency": "THB"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 45,
"totalPages": 5,
"hasNextPage": true,
"hasPrevPage": false
}
}Response Fields (each item in data array):
| Field | Type | Nullable | Constraints / Format | Description |
|---|---|---|---|---|
_id | string | No | MongoDB ObjectId (24 hex chars) | Unique claim identifier |
voucherId | object | No | Populated voucher summary | Associated voucher template |
voucherId._id | string | No | MongoDB ObjectId (24 hex chars) | Voucher template ID |
voucherId.name | string | No | Trimmed | Voucher display name |
voucherId.images | string[] | No | Array of URLs. May be [] | Voucher images |
voucherId.value | number | No | >= 0 | Voucher value |
voucherId.valueType | string | No | Enum: fixed, percentage | How value is applied |
userId | object | No | Populated user summary | User who received or claimed the voucher |
userId._id | string | No | MongoDB ObjectId (24 hex chars) | User ID |
userId.displayName | string | Yes | May be null if not set | User display name |
userId.email | string | Yes | Email format | User email |
userId.avatar | string | Yes | URL or null | User avatar |
tenantId | string | No | MongoDB ObjectId (24 hex chars) | Tenant that owns the claim record |
voucherOwnerTenantId | string | Yes | MongoDB ObjectId (24 hex chars) | Tenant that owns the voucher template |
nftTokenId | string | Yes | Blockchain token ID. May be null | NFT token minted for this claim |
status | string | No | Enum: active, claimed, redeemed, expired | Current claim status |
redemptionCode | string | No | Format: VCH-{base36_timestamp}-{4_random_chars}. Unique | Code for redeeming the voucher |
claimedAt | string | No | ISO 8601 datetime | When the voucher was claimed or sent |
redeemedAt | string | Yes | ISO 8601 datetime. May be null | When the voucher was redeemed |
expiresAt | string | Yes | ISO 8601 datetime. null = no expiry | When this claim expires |
campaignId | string | Yes | MongoDB ObjectId. May be null | Campaign associated with the claim, if any |
settlementAmount | number | No | >= 0. Default: 0 | Settlement amount captured at claim time |
settlementCurrency | string | No | ISO 4217 code | Settlement currency |
tokenAmountPaid | string | No | Numeric string. Default: "0" | Token amount paid at claim time |
redemptionDetails | object | Yes | Object or null | Redemption metadata after use |
externalVoucherCode | string | Yes | String or null | Fulfilled external voucher code |
externalRedemptionUrl | string | Yes | URL or null | Fulfilled external redemption URL |
externalOrderId | string | Yes | Provider order ID or null | External provider order ID |
externalBuyStatus | string | Yes | Enum: pending, recorded, failed, null | External buy status |
externalFulfillmentStatus | string | Yes | Enum: pending, processing, fulfilled, failed, null | External fulfillment status |
externalFulfillmentError | string | Yes | String or null | External fulfillment error details |
externalIsDirectRecharge | boolean | No | true or false. Default: false | Whether this fulfillment was direct recharge |
externalRechargeAccount | string | Yes | String or null | Direct recharge account, if applicable |
metadata | object | No | Object. Default: {} | Extended metadata |
createdAt | string | No | ISO 8601 datetime | Record creation timestamp |
updatedAt | string | No | ISO 8601 datetime | Last update timestamp |
Redeem Voucher by Code
Redeem a voucher using its redemption code. Use this when a customer presents their voucher code at your store or checkout.
POST /vouchers/redeem-by-codeRequest Body:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
redemptionCode | string | Yes | Min: 1 character. Case-sensitive | The unique redemption code from the voucher claim (e.g., VCH-M1ABC2-XY3Z) |
location | string | No | No max length | Where the redemption occurred (e.g., store name or branch) |
notes | string | No | No max length | Additional notes about the redemption (e.g., order number) |
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/vouchers/redeem-by-code" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"redemptionCode": "ABC123XYZ",
"location": "Store #42",
"notes": "Customer purchased item XYZ"
}'Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439014",
"voucherId": "507f1f77bcf86cd799439011",
"userId": "507f1f77bcf86cd799439015",
"status": "redeemed",
"redemptionCode": "VCH-M1ABC2-XY3Z",
"claimedAt": "2024-01-15T14:30:00.000Z",
"redeemedAt": "2024-02-01T10:15:00.000Z",
"redemptionDetails": {
"location": "Store #42",
"notes": "Customer purchased item XYZ",
"method": "api_key",
"redeemedBy": "api:507f1f77bcf86cd799439099",
"redeemedByType": "api_key",
"redeemedByApiKey": "507f1f77bcf86cd799439099"
},
"externalVoucherCode": "ABC-123-XYZ",
"externalRedemptionUrl": null,
"externalOrderId": "69f75fe31134f32e472b2f98",
"externalFulfillmentStatus": "fulfilled"
},
"message": "Voucher redeemed"
}Response Fields:
| Field | Type | Nullable | Constraints / Format | Description |
|---|---|---|---|---|
_id | string | No | MongoDB ObjectId (24 hex chars) | Claim identifier |
voucherId | string or object | No | MongoDB ObjectId or populated voucher object | Associated voucher template |
userId | string | No | MongoDB ObjectId (24 hex chars) | User who owned the voucher |
status | string | No | redeemed after successful redemption | Claim status |
redemptionCode | string | No | Format: VCH-{base36_timestamp}-{4_random_chars} | The redeemed code |
claimedAt | string | No | ISO 8601 datetime | When the voucher was originally claimed |
redeemedAt | string | No | ISO 8601 datetime | When the voucher was redeemed |
redemptionDetails.location | string | Yes | May be omitted if not provided in request | Where the redemption occurred |
redemptionDetails.notes | string | Yes | May be omitted if not provided in request | Additional redemption notes |
redemptionDetails.method | string | No | api_key | Redemption method |
redemptionDetails.redeemedBy | string | No | api:{apiKeyId} | API key actor recorded for the redemption |
redemptionDetails.redeemedByType | string | No | api_key | Actor type |
redemptionDetails.redeemedByApiKey | string | Yes | MongoDB ObjectId | API key ID, when available |
externalVoucherCode | string | Yes | String or null | Fulfilled external voucher code |
externalRedemptionUrl | string | Yes | URL or null | Fulfilled external redemption URL |
externalOrderId | string | Yes | Provider order ID or null | External provider order ID |
externalFulfillmentStatus | string | Yes | Enum: fulfilled, failed, processing, pending, null | External fulfillment status |
Get Voucher Info by Redemption Code
Retrieve voucher information using the redemption code before redeeming. Useful for verifying voucher details before processing.
GET /vouchers/redeem/:code/infoPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Redemption code |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/vouchers/redeem/ABC123XYZ/info" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"redemptionCode": "VCH-M1ABC2-XY3Z",
"status": "active",
"voucher": {
"name": "20% Off Discount",
"description": "Get 20% off your next purchase",
"value": 20,
"valueType": "percentage",
"valueCurrency": "THB",
"image": "https://example.com/image.jpg",
"terms": "Valid on orders over $50"
},
"tenant": {
"name": "Demo Tenant",
"logo": "https://example.com/logo.png",
"slug": "demo-tenant"
},
"pinPrefix": "VI",
"expiresAt": "2024-12-31T23:59:59.000Z",
"canRedeem": true
}
}Response Fields:
| Field | Type | Nullable | Constraints / Format | Description |
|---|---|---|---|---|
redemptionCode | string | No | Redemption code | The code that was looked up |
status | string | No | Enum: active, claimed, redeemed, expired | Display status. Expired active claims return expired |
voucher | object | Yes | Object or null | Voucher display summary |
voucher.name | string | No | Trimmed | Voucher display name |
voucher.description | string | No | May be empty "" | Voucher description |
voucher.value | number | No | >= 0 | Discount value |
voucher.valueType | string | No | Enum: fixed, percentage | How value is applied |
voucher.valueCurrency | string | No | ISO 4217 code | Currency for fixed-value vouchers |
voucher.image | string | Yes | URL or null | First voucher image |
voucher.terms | string | No | May be empty "" | Terms and conditions |
tenant | object | Yes | Object or null | Voucher owner tenant summary |
tenant.name | string | No | Text | Tenant name |
tenant.logo | string | Yes | URL or null | Tenant logo |
tenant.slug | string | Yes | Slug or null | Tenant slug |
pinPrefix | string | No | Text, e.g. VI | PIN prefix expected for staff PIN redemption |
expiresAt | string | Yes | ISO 8601 datetime. null = no expiry | When this claim expires |
canRedeem | boolean | No | true or false | Whether the current status allows redemption |
Consume Voucher by PIN
Consume a voucher using a staff PIN. This endpoint verifies the staff member's identity via their personal PIN before marking the voucher as consumed. Use this when staff members need to authenticate themselves at the point of consumption.
Important: PIN verification is scoped to the voucher creator's organization. The PIN is validated against the tenant (or sub-company) that created the voucher, not the organization that distributed it via a campaign. In cross-tenant scenarios (e.g., Company A creates a voucher and Company B distributes it through their campaign), the member must present the voucher at Company A's store where Company A's staff enters their PIN. If the voucher was created by a sub-company, only that sub-company's PINs are valid.
PIN Permission Requirement: The staff PIN must have the
voucher_redemptionpermission to consume vouchers. PINs that only have thetoken_claimpermission cannot be used for voucher consumption. See the Admin Portal documentation for details on configuring PIN permissions.
Redeem by Code vs Consume by PIN
- Redeem by Code (
POST /vouchers/redeem-by-code): Uses the API key for authorization. The API key owner is recorded as the redeemer. Best for server-to-server integrations.- Consume by PIN (
POST /vouchers/redeem/:code/pin): Requires a staff PIN for verification. The specific staff member is recorded. Best for in-store scenarios where individual staff accountability is needed.
POST /vouchers/redeem/:code/pinPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Yes | The redemption code from the voucher claim |
Request Body:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
pin | string | Yes | Min: 5 characters. Max: 20 characters. Typical format: 2-letter prefix + 4-digit PIN (e.g., HA1234). Must have voucher_redemption permission | Staff PIN for identity verification at point of consumption |
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/vouchers/redeem/ABC123XYZ/pin" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"pin": "HA1234"
}'Example Response:
{
"success": true,
"data": {
"success": true,
"voucher": {
"name": "20% Off Discount",
"value": 20,
"valueType": "percentage",
"valueCurrency": "THB"
},
"redeemedAt": "2024-02-01T10:15:00.000Z",
"redeemedBy": "John Staff"
},
"message": "Voucher consumed"
}Response Fields:
| Field | Type | Nullable | Constraints / Format | Description |
|---|---|---|---|---|
success | boolean | No | Always true on success | Consumption result |
voucher.name | string | No | Trimmed | Name of the consumed voucher |
voucher.value | number | No | >= 0 | Discount value |
voucher.valueType | string | No | Enum: fixed, percentage | How value is applied |
voucher.valueCurrency | string | No | ISO 4217 code (e.g., THB, HKD) | Currency for fixed-value vouchers |
redeemedAt | string | No | ISO 8601 datetime | When the voucher was consumed |
redeemedBy | string | No | Staff member's display name | Name of the staff who verified the PIN |
Error Responses:
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid PIN format (must be 5-20 characters) |
| 400 | VALIDATION_ERROR | Invalid PIN (wrong prefix or PIN doesn't match any staff) |
| 400 | VALIDATION_ERROR | PIN lacks voucher_redemption permission |
| 400 | VALIDATION_ERROR | Voucher is already redeemed or expired |
| 404 | NOT_FOUND | Voucher not found with the given redemption code |
7. API Reference: Users
Required Scope: users
List Users
Retrieve a paginated list of users for your tenant.
GET /usersQuery Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (default: 1) |
limit | integer | No | Items per page (default: 20) |
role | string | No | Filter by role: super_admin, tenant_admin, sub_company_admin, member |
search | string | No | Search by email, phone, or name |
isActive | boolean | No | Filter by active status |
subCompanyId | string | No | Filter by sub-company |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/users?role=member&isActive=true&limit=10" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439015",
"email": "john@example.com",
"phone": "+66812345678",
"displayName": "John Doe",
"role": "member",
"isActive": true,
"walletAddress": "0x1234567890abcdef...",
"registrationSource": "direct",
"storeId": null,
"campaignId": null,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 150,
"totalPages": 15,
"hasNextPage": true,
"hasPrevPage": false
}
}Create User
Create a new user for your tenant.
POST /usersRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
email | string | No* | User email address |
phone | string | No* | User phone number |
password | string | Yes | Password (min 6 characters) |
displayName | string | No | User display name |
role | string | No | member or sub_company_admin (default: member) |
subCompanyId | string | No | Sub-company ID to assign user to |
metadata | object | No | Custom metadata key-value pairs |
*At least one of
phoneis required.
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/users" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"email": "newuser@example.com",
"phone": "+66812345678",
"password": "securepassword123",
"displayName": "New User",
"role": "member",
"metadata": {
"referralSource": "website",
"tier": "gold"
}
}'Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439016",
"email": "newuser@example.com",
"phone": "+66812345678",
"displayName": "New User",
"role": "member",
"isActive": true,
"walletAddress": "0xabcdef1234567890...",
"metadata": {
"referralSource": "website",
"tier": "gold"
},
"createdAt": "2024-02-01T12:00:00.000Z"
},
"message": "User created"
}Get User Details
Retrieve details of a specific user.
GET /users/:userIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User ID |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/users/507f1f77bcf86cd799439015" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439015",
"email": "john@example.com",
"phone": "+66812345678",
"displayName": "John Doe",
"role": "member",
"isActive": true,
"walletAddress": "0x1234567890abcdef...",
"avatar": "https://example.com/avatar.jpg",
"metadata": {
"tier": "gold"
},
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
}Update User
Update an existing user.
PATCH /users/:userIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User ID |
Request Body:
| Field | Type | Description |
|---|---|---|
displayName | string | User display name |
avatar | string | Avatar URL |
isActive | boolean | Active status |
subCompanyId | string | Sub-company ID |
metadata | object | Custom metadata |
Example Request:
curl -X PATCH "https://your-domain.com/api/external/v1/users/507f1f77bcf86cd799439015" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"displayName": "John Smith",
"metadata": {
"tier": "platinum"
}
}'Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439015",
"displayName": "John Smith",
"metadata": {
"tier": "platinum"
},
"updatedAt": "2024-02-01T12:00:00.000Z"
},
"message": "User updated"
}Deactivate User
Soft delete a user (sets isActive to false).
DELETE /users/:userIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User ID |
Example Request:
curl -X DELETE "https://your-domain.com/api/external/v1/users/507f1f77bcf86cd799439015" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": null,
"message": "User deactivated"
}Get User Token Balances
Retrieve all token balances for a specific user.
GET /users/:userId/balancesPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User ID |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/users/507f1f77bcf86cd799439015/balances" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": [
{
"tokenId": "507f1f77bcf86cd799439020",
"tokenName": "Loyalty Points",
"symbol": "LP",
"balance": "1500",
"lockedBalance": "0"
},
{
"tokenId": "507f1f77bcf86cd799439021",
"tokenName": "Reward Coins",
"symbol": "RC",
"balance": "250",
"lockedBalance": "50"
}
]
}Get User Vouchers
Retrieve all vouchers claimed by a specific user.
GET /users/:userId/vouchersPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User ID |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (default: 1) |
limit | integer | No | Items per page (default: 20) |
status | string | No | Filter by status: active, redeemed, expired |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/users/507f1f77bcf86cd799439015/vouchers?status=active" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439014",
"voucherId": "507f1f77bcf86cd799439011",
"status": "active",
"redemptionCode": "ABC123XYZ",
"claimedAt": "2024-01-15T14:30:00.000Z",
"expiresAt": "2024-12-31T23:59:59.000Z",
"voucher": {
"name": "20% Off Discount",
"value": 20,
"valueType": "percentage"
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 5,
"totalPages": 1,
"hasNextPage": false,
"hasPrevPage": false
}
}Search User by Identifier
Find a user by email, phone, or wallet address.
GET /users/search/by-identifierQuery Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | No* | User email address |
phone | string | No* | User phone number |
walletAddress | string | No* | User wallet address |
*At least one identifier is required.
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/users/search/by-identifier?email=john@example.com" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439015",
"email": "john@example.com",
"phone": "+66812345678",
"displayName": "John Doe",
"role": "member",
"isActive": true,
"walletAddress": "0x1234567890abcdef..."
}
}8. API Reference: Campaigns
Required Scope: campaigns
List Campaigns
Retrieve a paginated list of campaigns.
GET /campaignsQuery Parameters:
| Parameter | Type | Required | Constraints | Description |
|---|---|---|---|---|
page | string | No | Parsed as integer. Min: 1. Default: 1 | Page number (1-indexed) |
limit | string | No | Parsed as integer. Min: 1. Default: 20 | Items per page |
search | string | No | Free-form string | Search by campaign name (partial match) |
isActive | string | No | String "true" or "false" (parsed as boolean) | Filter by active status |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/campaigns?isActive=true&limit=10" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439030",
"name": "Summer Promotion",
"description": "Summer 2024 voucher campaign",
"slug": "summer-promotion",
"tokenId": "507f1f77bcf86cd799439020",
"isActive": true,
"isPublic": true,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-08-31T23:59:59.000Z",
"images": ["https://example.com/campaign.jpg"],
"createdAt": "2024-05-15T10:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 5,
"totalPages": 1,
"hasNextPage": false,
"hasPrevPage": false
}
}Response Fields (each item in data array):
| Field | Type | Nullable | Constraints / Format | Description |
|---|---|---|---|---|
_id | string | No | MongoDB ObjectId (24 hex chars) | Unique identifier for the campaign |
name | string | No | Min: 1 char. Trimmed | Campaign display name |
description | string | No | May be empty "" | Campaign description text |
slug | string | No | Lowercase, URL-safe. Unique per tenant + sub-company | URL-friendly identifier for campaign pages (e.g., summer-promotion) |
tokenId | string | No | MongoDB ObjectId (24 hex chars) | Token users spend to claim vouchers in this campaign |
isActive | boolean | No | true or false | Whether the campaign is currently active |
isPublic | boolean | No | true or false | Whether the campaign is publicly accessible without authentication |
startDate | string | Yes | ISO 8601 datetime. Default: creation time | When the campaign starts |
endDate | string | Yes | ISO 8601 datetime. null = no end date | When the campaign ends |
images | string[] | No | Array of valid URLs. May be empty [] | Campaign banner/cover images |
createdAt | string | No | ISO 8601 datetime | Creation timestamp |
Create Campaign
Create a new voucher campaign.
POST /campaignsRequest Body:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
name | string | Yes | Min: 1 character. Trimmed | Campaign display name. Used to auto-generate slug if not provided |
description | string | No | No max length. Default: "" | Campaign description text |
slug | string | No | Lowercase, URL-safe characters only. Unique per tenant + sub-company. Auto-generated from name via slugify if omitted | URL-friendly identifier used in campaign page URLs (e.g., summer-promotion-2024) |
tokenId | string | Yes | Min: 1 character. MongoDB ObjectId (24 hex chars). Must reference an existing Token | Token that users spend to claim vouchers in this campaign |
startDate | string | No | ISO 8601 datetime (e.g., 2024-06-01T00:00:00.000Z). Default: current time | When the campaign starts accepting claims |
endDate | string | No | ISO 8601 datetime. Should be after startDate | When the campaign ends. Omit for no end date |
isActive | boolean | No | Default: true | Whether the campaign is active. Inactive campaigns are hidden from users |
isPublic | boolean | No | Default: true | Whether the campaign appears on public pages (accessible without login) |
images | string[] | No | Each element must be a valid URL. May be empty [] | Campaign banner/cover image URLs. First image is used as primary display |
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/campaigns" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Promotion 2024",
"description": "Exclusive summer deals for loyal customers",
"tokenId": "507f1f77bcf86cd799439020",
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-08-31T23:59:59.000Z",
"isPublic": true
}'Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439031",
"name": "Summer Promotion 2024",
"description": "Exclusive summer deals for loyal customers",
"slug": "summer-promotion-2024",
"tokenId": "507f1f77bcf86cd799439020",
"isActive": true,
"isPublic": true,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-08-31T23:59:59.000Z",
"createdAt": "2024-05-15T10:00:00.000Z"
},
"message": "Campaign created"
}Response Fields:
Returns the newly created campaign object. See List Campaigns Response Fields for field descriptions.
Get Campaign Details
Retrieve details of a specific campaign.
GET /campaigns/:campaignIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
campaignId | string | Yes | Campaign ID |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439030",
"name": "Summer Promotion",
"description": "Summer 2024 voucher campaign",
"slug": "summer-promotion",
"tokenId": "507f1f77bcf86cd799439020",
"isActive": true,
"isPublic": true,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-08-31T23:59:59.000Z",
"images": ["https://example.com/campaign.jpg"],
"createdAt": "2024-05-15T10:00:00.000Z"
}
}Response Fields:
Returns the full campaign object. See List Campaigns Response Fields for field descriptions.
Update Campaign
Update an existing campaign.
PATCH /campaigns/:campaignIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
campaignId | string | Yes | Campaign ID |
Request Body:
| Field | Type | Constraints | Description |
|---|---|---|---|
name | string | Min: 1 character. Trimmed | Campaign display name |
description | string | No max length | Campaign description |
tokenId | string | MongoDB ObjectId (24 hex chars). Must reference existing Token | Token for the campaign |
startDate | string | ISO 8601 datetime | Campaign start date |
endDate | string | ISO 8601 datetime | Campaign end date |
isActive | boolean | true or false | Active status |
isPublic | boolean | true or false | Public visibility |
images | string[] | Each element must be a valid URL | Campaign image URLs |
Example Request:
curl -X PATCH "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Extended Summer Promotion",
"endDate": "2024-09-30T23:59:59.000Z"
}'Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439030",
"name": "Extended Summer Promotion",
"endDate": "2024-09-30T23:59:59.000Z",
"updatedAt": "2024-08-15T10:00:00.000Z"
},
"message": "Campaign updated"
}Delete Campaign
Delete a campaign.
DELETE /campaigns/:campaignIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
campaignId | string | Yes | Campaign ID |
Example Request:
curl -X DELETE "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439030"
},
"message": "Campaign deleted"
}Response Fields:
| Field | Type | Constraints / Format | Description |
|---|---|---|---|
_id | string | MongoDB ObjectId (24 hex chars) | ID of the deleted campaign |
Get Campaign Vouchers
Retrieve vouchers associated with a campaign.
GET /campaigns/:campaignId/vouchersPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
campaignId | string | Yes | Campaign ID |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (default: 1) |
limit | integer | No | Items per page (default: 20) |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030/vouchers" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439011",
"name": "20% Off Discount",
"value": 20,
"valueType": "percentage",
"tokenAmount": "100",
"sortOrder": 1,
"isActive": true
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 3,
"totalPages": 1,
"hasNextPage": false,
"hasPrevPage": false
}
}Response Fields (each item in data array):
| Field | Type | Nullable | Constraints / Format | Description |
|---|---|---|---|---|
_id | string | No | MongoDB ObjectId (24 hex chars) | Voucher template ID |
name | string | No | Trimmed | Voucher display name |
value | number | No | >= 0 | Discount value (see valueType) |
valueType | string | No | Enum: fixed, percentage | How value is applied |
tokenAmount | string | No | Stored as string for precision. "0" = no campaign-level cap | Campaign stock/quantity cap for this voucher |
sortOrder | integer | No | Default: 0. Lower = first | Display order within the campaign |
isActive | boolean | No | true or false | Whether this voucher is currently claimable within the campaign |
Add Vouchers to Campaign
Add vouchers to a campaign with token pricing.
POST /campaigns/:campaignId/vouchersPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
campaignId | string | Yes | Campaign ID |
Request Body:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
voucherIds | string[] | Yes | Min: 1 element. Each: MongoDB ObjectId (24 hex chars). Duplicates in same campaign are rejected | Array of voucher IDs to add to the campaign |
tokenAmount | string | Yes | Min: 1 character. Stored as string for precision. "0" = no campaign-level cap | Campaign stock/quantity cap for these vouchers |
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030/vouchers" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"voucherIds": ["507f1f77bcf86cd799439011", "507f1f77bcf86cd799439012"],
"tokenAmount": "100"
}'Example Response:
{
"success": true,
"data": {
"added": 2
},
"message": "Vouchers added to campaign"
}Response Fields:
| Field | Type | Constraints / Format | Description |
|---|---|---|---|
added | integer | >= 0 | Number of vouchers successfully added to campaign |
Update Campaign Voucher
Update a voucher's settings within a campaign.
PATCH /campaigns/:campaignId/vouchers/:voucherIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
campaignId | string | Yes | Campaign ID |
voucherId | string | Yes | Voucher ID |
Request Body:
| Field | Type | Constraints | Description |
|---|---|---|---|
tokenAmount | string | Stored as string for precision. "0" = no campaign-level cap | Campaign stock/quantity cap for this voucher |
sortOrder | integer | Number type (can be negative). Default: 0. Lower = first | Display order within the campaign |
isActive | boolean | true or false | Whether this voucher is claimable in the campaign. Can be temporarily disabled |
Example Request:
curl -X PATCH "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030/vouchers/507f1f77bcf86cd799439011" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"tokenAmount": "150",
"sortOrder": 1
}'Example Response:
{
"success": true,
"data": {
"campaignId": "507f1f77bcf86cd799439030",
"voucherId": "507f1f77bcf86cd799439011",
"tokenAmount": "150",
"sortOrder": 1
},
"message": "Campaign voucher updated"
}Response Fields:
| Field | Type | Constraints / Format | Description |
|---|---|---|---|
campaignId | string | MongoDB ObjectId (24 hex chars) | Campaign ID |
voucherId | string | MongoDB ObjectId (24 hex chars) | Voucher ID |
tokenAmount | string | String for precision. "0" = no cap | Updated campaign quantity cap |
sortOrder | integer | Lower = first | Updated display order |
Remove Voucher from Campaign
Remove a voucher from a campaign.
DELETE /campaigns/:campaignId/vouchers/:voucherIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
campaignId | string | Yes | Campaign ID |
voucherId | string | Yes | Voucher ID |
Example Request:
curl -X DELETE "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030/vouchers/507f1f77bcf86cd799439011" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": null,
"message": "Voucher removed from campaign"
}Get Available Vouchers for Campaign
Get vouchers that can be added to a campaign (not already in the campaign).
GET /campaigns/:campaignId/available-vouchersPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
campaignId | string | Yes | Campaign ID |
Query Parameters:
| Parameter | Type | Required | Constraints | Description |
|---|---|---|---|---|
page | string | No | Parsed as integer. Min: 1. Default: 1 | Page number (1-indexed) |
limit | string | No | Parsed as integer. Min: 1. Default: 20 | Items per page |
search | string | No | Free-form string | Search by voucher name (partial match) |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030/available-vouchers?search=discount" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439013",
"name": "10% New User Discount",
"value": 10,
"valueType": "percentage",
"isActive": true
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 10,
"totalPages": 1,
"hasNextPage": false,
"hasPrevPage": false
}
}Response Fields (each item in data array):
| Field | Type | Nullable | Constraints / Format | Description |
|---|---|---|---|---|
_id | string | No | MongoDB ObjectId (24 hex chars) | Voucher template ID |
name | string | No | Trimmed | Voucher display name |
value | number | No | >= 0 | Discount value |
valueType | string | No | Enum: fixed, percentage | How value is applied |
isActive | boolean | No | true or false | Whether the voucher is active |
9. API Reference: Tokens
Required Scope: tokens
List Tokens
Retrieve a list of tokens for your tenant.
GET /tokensQuery Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (default: 1) |
limit | integer | No | Items per page (default: 20) |
isActive | boolean | No | Filter by active status |
subCompanyId | string | No | Filter by sub-company |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/tokens?isActive=true" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439020",
"name": "Loyalty Points",
"symbol": "LP",
"description": "Earn points on every purchase",
"totalSupply": "1000000",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 2,
"totalPages": 1,
"hasNextPage": false,
"hasPrevPage": false
}
}Get Token Details
Retrieve details of a specific token.
GET /tokens/:tokenIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenId | string | Yes | Token ID |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439020",
"name": "Loyalty Points",
"symbol": "LP",
"description": "Earn points on every purchase",
"totalSupply": "1000000",
"isActive": true,
"decimals": 0,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
}Get Token Statistics
Retrieve statistics for a specific token.
GET /tokens/:tokenId/statsPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenId | string | Yes | Token ID |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/stats" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"totalSupply": "1000000",
"circulatingSupply": "750000",
"holdersCount": 1250,
"totalTransactions": 15000,
"averageBalance": "600"
}
}Get Token Holders
Retrieve a list of token holders with their balances.
GET /tokens/:tokenId/holdersPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenId | string | Yes | Token ID |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (default: 1) |
limit | integer | No | Items per page (default: 20) |
sort | string | No | Sort by: balance, name, recent (default: balance) |
search | string | No | Search by user name or email |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/holders?sort=balance&limit=10" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": [
{
"user": {
"_id": "507f1f77bcf86cd799439015",
"displayName": "John Doe",
"email": "john@example.com"
},
"balance": "5000"
},
{
"user": {
"_id": "507f1f77bcf86cd799439016",
"displayName": "Jane Smith",
"email": "jane@example.com"
},
"balance": "3500"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 1250,
"totalPages": 125,
"hasNextPage": true,
"hasPrevPage": false
},
"token": {
"_id": "507f1f77bcf86cd799439020",
"name": "Loyalty Points",
"symbol": "LP"
}
}Mint Tokens
Create new tokens and add them to a user's balance.
POST /tokens/:tokenId/mintPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenId | string | Yes | Token ID |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
toUserId | string | Yes | User ID to receive tokens |
amount | string | Yes | Amount to mint |
memo | string | No | Transaction memo |
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/mint" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"toUserId": "507f1f77bcf86cd799439015",
"amount": "1000",
"memo": "Welcome bonus"
}'Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439040",
"tokenId": "507f1f77bcf86cd799439020",
"type": "mint",
"amount": "1000",
"toUserId": "507f1f77bcf86cd799439015",
"memo": "Welcome bonus",
"createdAt": "2024-02-01T12:00:00.000Z"
},
"message": "Tokens minted successfully"
}Burn Tokens
Remove tokens from a user's balance.
POST /tokens/:tokenId/burnPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenId | string | Yes | Token ID |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
fromUserId | string | Yes | User ID to burn from |
amount | string | Yes | Amount to burn |
memo | string | No | Transaction memo |
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/burn" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"fromUserId": "507f1f77bcf86cd799439015",
"amount": "500",
"memo": "Redemption for reward"
}'Example Response:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439041",
"tokenId": "507f1f77bcf86cd799439020",
"type": "burn",
"amount": "500",
"fromUserId": "507f1f77bcf86cd799439015",
"memo": "Redemption for reward",
"createdAt": "2024-02-01T12:00:00.000Z"
},
"message": "Tokens burned successfully"
}Adjust User Balance
Send or recall tokens between the company admin balance and a user.
POST /tokens/:tokenId/adjustPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenId | string | Yes | Token ID |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User ID |
amount | string | Yes | Amount to adjust |
operation | string | Yes | send or recall |
reason | string | Yes | Reason for adjustment |
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/adjust" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"userId": "507f1f77bcf86cd799439015",
"amount": "100",
"operation": "send",
"reason": "Manual adjustment for customer service"
}'Example Response:
{
"success": true,
"data": {
"userBalance": "1100",
"adminBalance": "8900",
"transaction": {
"_id": "507f1f77bcf86cd799439042",
"tokenId": "507f1f77bcf86cd799439020",
"type": "transfer",
"amount": "100"
}
},
"message": "Balance sent"
}Expiry Behavior: When sending tokens, the recipient's lots always inherit the expiry from the source lots (defined at mint time). Expiry dates cannot be overridden during send or recall operations. When recalling tokens, the returned lots inherit the expiry from the user's lots, keeping ledger and sendable balances in sync.
Send Tokens
Send tokens from the company admin balance pool to a user. This is a simplified version of the adjust endpoint with the operation fixed to send.
POST /tokens/:tokenId/sendPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenId | string | Yes | Token ID |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User ID to send tokens to |
amount | string | Yes | Amount of tokens to send |
reason | string | No | Reason for sending tokens |
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/send" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"userId": "507f1f77bcf86cd799439015",
"amount": "1000",
"reason": "Loyalty reward"
}'Example Response:
{
"success": true,
"data": {
"userBalance": "2000",
"adminBalance": "8000",
"transaction": {
"_id": "507f1f77bcf86cd799439043",
"tokenId": "507f1f77bcf86cd799439020",
"type": "transfer",
"amount": "1000"
}
},
"message": "Tokens sent to user"
}Error Responses:
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Insufficient company balance for sending tokens |
| 404 | NOT_FOUND | Token or user not found |
Recall Tokens
Recall tokens from a user back to the company admin balance pool. This is a simplified version of the adjust endpoint with the operation fixed to recall.
POST /tokens/:tokenId/recallPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenId | string | Yes | Token ID |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User ID to recall tokens from |
amount | string | Yes | Amount of tokens to recall |
reason | string | No | Reason for recalling tokens |
Example Request:
curl -X POST "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/recall" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"userId": "507f1f77bcf86cd799439015",
"amount": "500",
"reason": "Balance correction"
}'Example Response:
{
"success": true,
"data": {
"userBalance": "1500",
"adminBalance": "8500",
"transaction": {
"_id": "507f1f77bcf86cd799439044",
"tokenId": "507f1f77bcf86cd799439020",
"type": "transfer",
"amount": "500"
}
},
"message": "Tokens recalled from user"
}Error Responses:
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Insufficient user balance for recalling tokens |
| 404 | NOT_FOUND | Token or user not found |
List Token Transactions
Retrieve a list of token transactions.
GET /tokens/transactions/listQuery Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (default: 1) |
limit | integer | No | Items per page (default: 20) |
tokenId | string | No | Filter by token ID |
type | string | No | Filter by type: mint, transfer, burn, reward, redeem |
fromDate | datetime | No | Filter from this date |
toDate | datetime | No | Filter until this date |
search | string | No | Search by user name or memo |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/tokens/transactions/list?type=mint&limit=10" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439040",
"tokenId": "507f1f77bcf86cd799439020",
"type": "mint",
"amount": "1000",
"toUserId": "507f1f77bcf86cd799439015",
"memo": "Welcome bonus",
"createdAt": "2024-02-01T12:00:00.000Z",
"token": {
"name": "Loyalty Points",
"symbol": "LP"
},
"toUser": {
"displayName": "John Doe",
"email": "john@example.com"
}
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 500,
"totalPages": 50,
"hasNextPage": true,
"hasPrevPage": false
}
}Get User Token Balances
Retrieve all token balances for a specific user.
GET /tokens/balance/user/:userIdPath Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User ID |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/tokens/balance/user/507f1f77bcf86cd799439015" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": [
{
"tokenId": "507f1f77bcf86cd799439020",
"tokenName": "Loyalty Points",
"symbol": "LP",
"balance": "1500",
"lockedBalance": "0"
},
{
"tokenId": "507f1f77bcf86cd799439021",
"tokenName": "Reward Coins",
"symbol": "RC",
"balance": "250",
"lockedBalance": "50"
}
]
}10. API Reference: Analytics
Required Scope: analytics
Get Analytics Overview
Get overall tenant analytics including users, vouchers, and transactions.
GET /analytics/overviewExample Request:
curl -X GET "https://your-domain.com/api/external/v1/analytics/overview" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"users": {
"total": 5000,
"active": 4500,
"newThisMonth": 250
},
"vouchers": {
"totalClaims": 12000,
"redeemed": 8500,
"active": 3500,
"redemptionRate": "70.83%"
},
"transactions": {
"total": 25000,
"thisMonth": 3500
},
"generatedAt": "2024-02-01T12:00:00.000Z"
}
}Get Voucher Analytics
Get detailed voucher analytics for a date range.
GET /analytics/vouchersQuery Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
fromDate | datetime | No | Start date for analytics |
toDate | datetime | No | End date for analytics |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/analytics/vouchers?fromDate=2024-01-01T00:00:00.000Z&toDate=2024-01-31T23:59:59.000Z" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"totalVouchers": 50,
"totalClaims": 1200,
"totalRedemptions": 850,
"redemptionRate": "70.83%",
"claimsByDay": [
{ "date": "2024-01-01", "count": 45 },
{ "date": "2024-01-02", "count": 52 }
],
"redemptionsByDay": [
{ "date": "2024-01-01", "count": 30 },
{ "date": "2024-01-02", "count": 38 }
],
"topVouchers": [
{
"voucherId": "507f1f77bcf86cd799439011",
"name": "20% Off Discount",
"claims": 250,
"redemptions": 180
}
]
}
}Get User Analytics
Get detailed user analytics for a date range.
GET /analytics/usersQuery Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
fromDate | datetime | No | Start date for analytics |
toDate | datetime | No | End date for analytics |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/analytics/users?fromDate=2024-01-01T00:00:00.000Z&toDate=2024-01-31T23:59:59.000Z" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"summary": {
"totalUsers": 5000,
"newUsersInPeriod": 500,
"activeUsers": 4500
},
"byRole": {
"member": 4800,
"sub_company_admin": 150,
"tenant_admin": 50
},
"dailySignups": [
{ "date": "2024-01-01", "count": 15 },
{ "date": "2024-01-02", "count": 22 }
]
}
}Get Token Analytics
Get detailed token analytics for a date range.
GET /analytics/tokensQuery Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
fromDate | datetime | No | Start date for analytics |
toDate | datetime | No | End date for analytics |
Example Request:
curl -X GET "https://your-domain.com/api/external/v1/analytics/tokens?fromDate=2024-01-01T00:00:00.000Z&toDate=2024-01-31T23:59:59.000Z" \
-H "X-API-Key: vio_live_your_api_key_here"Example Response:
{
"success": true,
"data": {
"summary": {
"totalTransactions": 3500,
"totalMinted": "500000",
"totalBurned": "150000",
"netChange": "350000"
},
"dailyTransactions": [
{ "date": "2024-01-01", "count": 120, "volume": "15000" },
{ "date": "2024-01-02", "count": 145, "volume": "18500" }
],
"byType": {
"mint": 800,
"transfer": 2000,
"burn": 400,
"reward": 200,
"redeem": 100
}
}
}11. Data Models
Voucher
| Field | Type | Constraints / Format | Description |
|---|---|---|---|
_id | string | MongoDB ObjectId (24 hex chars) | Unique identifier |
name | string | Min: 1 char. Trimmed | Voucher display name |
description | string | May be empty "". No max length | Voucher description (plain text) |
value | number | >= 0. Default: 0 | Discount value. Interpretation depends on valueType |
valueType | string | Enum: fixed, percentage. Default: fixed | How value is applied: flat amount or percentage rate |
valueCurrency | string | ISO 4217 code (e.g., THB, HKD, USD). Default: THB | Currency for fixed-value vouchers |
voucherType | string | Enum: cash, discount, product, cash_discount | Voucher business category |
consumptionType | string | Enum: vio_code, coupon_code, url, qr_code, manual, zhichong | Voucher consumption mode |
terms | string | May be empty "". No max length | Terms and conditions |
images | string[] | Each element: valid URL. May be empty [] | Image URLs. First image is primary display |
isActive | boolean | Default: true | Whether voucher is active and claimable |
isTransferable | boolean | Default: false | Whether claimed vouchers can be transferred |
totalQuantity | integer | -1 = unlimited; >= 0 = limited. Default: -1 | Total available for claiming |
claimedQuantity | integer | >= 0. Default: 0 | Number already claimed |
maxClaimsPerUser | integer | 0 = unlimited; >= 1 = limited. Default: 1 | Max claims per individual user |
startDate | string | ISO 8601 datetime. Default: creation time | When voucher becomes valid |
endDate | string | ISO 8601 datetime. null = no expiry | When voucher expires |
visibility | string | Enum: private, public, shared. Default: private | Access scope. Also controls transfer scope between users |
category | string | Enum: Wellness, Health, Food & Beverage, Leisure & Entertainment, Travel & Hospitality, Lifestyle & Services, Others. Nullable | Primary voucher category |
categories | string[] | Array of strings. Each trimmed | Category tags for filtering |
minSpend | number | >= 0. Default: 0 | Minimum purchase amount required. 0 = no minimum |
maxDiscount | number | Must be > 0 when set. Nullable | Max discount cap (useful for percentage vouchers) |
settlementAmount | number | >= 0. Default: 0 | Settlement amount for cross-tenant accounting |
settlementCurrency | string | ISO 4217 code. Default: THB | Settlement currency |
externalProvider | string | Provider code. Nullable | External voucher provider, e.g. vouchain |
externalId | string | Provider-side template ID. Nullable | External voucher template ID |
externalRequiresDirectOrderParams | boolean | Default: false | Whether external redemption requires extra account params |
applicableScope | string | Enum: all_outlets, partial_outlets, single_store | Store applicability scope |
bookingEnabled | boolean | Default: false | Whether booking is enabled |
bookingDaysInAdvance | integer | >= 0. Default: 0 | How many days in advance users can book |
createdAt | string | ISO 8601 datetime. Auto-generated | Creation timestamp |
updatedAt | string | ISO 8601 datetime. Auto-updated | Last update timestamp |
VoucherClaim (UserVoucher)
| Field | Type | Constraints / Format | Description |
|---|---|---|---|
_id | string | MongoDB ObjectId (24 hex chars) | Unique identifier |
voucherId | string | MongoDB ObjectId (24 hex chars) | Associated voucher template ID |
userId | string | MongoDB ObjectId (24 hex chars) | Owner user ID |
status | string | Enum: active, claimed, redeemed, expired. Default: active | Current claim status |
redemptionCode | string | Unique. Auto-generated format: VCH-{base36_timestamp}-{4_random_chars} | Code for redeeming the voucher |
claimedAt | string | ISO 8601 datetime. Default: current time | When the voucher was claimed |
redeemedAt | string | ISO 8601 datetime. null until redeemed | When the voucher was redeemed |
expiresAt | string | ISO 8601 datetime. null = no expiry. Inherited from voucher's endDate | When this claim expires |
redemptionDetails.location | string | Optional | Where the voucher was redeemed |
redemptionDetails.notes | string | Optional | Redemption notes |
redemptionDetails.method | string | Optional | Redemption method, e.g. api_key or pin |
externalVoucherCode | string | Nullable | Fulfilled external voucher code |
externalRedemptionUrl | string | Nullable | Fulfilled external redemption URL |
externalOrderId | string | Nullable | External provider order ID |
externalFulfillmentStatus | string | Enum: pending, processing, fulfilled, failed, null | External fulfillment status |
User
| Field | Type | Constraints / Format | Description |
|---|---|---|---|
_id | string | MongoDB ObjectId (24 hex chars) | Unique identifier |
email | string | Email format. null if not set. At least one of email/phone required | Email address |
phone | string | Phone number format. null if not set | Phone number |
displayName | string | null if not set. Trimmed | Display name |
avatar | string | Valid URL. null if not set | Avatar URL |
role | string | Enum: member, sub_company_admin, tenant_admin, super_admin | User role |
isActive | boolean | Default: true | Active status |
walletAddress | string | Ethereum address format (0x...). Auto-provisioned on creation | Web3 custodial wallet address |
registrationSource | string | Enum: created, direct, store, campaign | How the user was registered |
storeId | object | Populated reference or null. Contains _id (ObjectId), name (string) | Store if registered via store |
campaignId | object | Populated reference or null. Contains _id (ObjectId), name (string), slug (string) | Campaign if registered via campaign |
metadata | object | Key-value pairs. Values can be any type | Custom metadata |
createdAt | string | ISO 8601 datetime. Auto-generated | Registration timestamp |
updatedAt | string | ISO 8601 datetime. Auto-updated | Last update timestamp |
Campaign
| Field | Type | Constraints / Format | Description |
|---|---|---|---|
_id | string | MongoDB ObjectId (24 hex chars) | Unique identifier |
name | string | Min: 1 char. Trimmed | Campaign display name |
description | string | May be empty "". No max length | Campaign description (plain text) |
slug | string | Lowercase, URL-safe. Unique per tenant + sub-company. Auto-generated from name | URL-friendly identifier for campaign pages |
tokenId | string | MongoDB ObjectId (24 hex chars). Required | Token users spend to claim vouchers in this campaign |
isActive | boolean | Default: true | Whether campaign is active. Inactive = hidden from users |
isPublic | boolean | Default: true | Whether campaign is publicly accessible (no login required) |
startDate | string | ISO 8601 datetime. Default: creation time | When the campaign starts |
endDate | string | ISO 8601 datetime. null = no end date | When the campaign ends |
images | string[] | Each element: valid URL. May be empty [] | Campaign banner/cover images |
createdAt | string | ISO 8601 datetime. Auto-generated | Creation timestamp |
Token
| Field | Type | Constraints / Format | Description |
|---|---|---|---|
_id | string | MongoDB ObjectId (24 hex chars) | Unique identifier |
name | string | Trimmed | Token display name |
symbol | string | Typically 2-5 uppercase characters | Token symbol (e.g., LP, RC) |
description | string | May be empty "". No max length | Token description |
totalSupply | string | Numeric string for precision | Total supply |
decimals | integer | >= 0. Default: 0 | Decimal places |
isActive | boolean | Default: true | Active status |
createdAt | string | ISO 8601 datetime. Auto-generated | Creation timestamp |
updatedAt | string | ISO 8601 datetime. Auto-updated | Last update timestamp |
TokenBalance
| Field | Type | Constraints / Format | Description |
|---|---|---|---|
tokenId | string | MongoDB ObjectId (24 hex chars) | Token ID |
tokenName | string | Trimmed | Token display name |
symbol | string | Typically 2-5 uppercase characters | Token symbol |
balance | string | Numeric string for precision. >= "0" | Available (spendable) balance |
lockedBalance | string | Numeric string for precision. >= "0" | Balance locked in pending operations |
Transaction
| Field | Type | Constraints / Format | Description |
|---|---|---|---|
_id | string | MongoDB ObjectId (24 hex chars) | Unique identifier |
tokenId | string | MongoDB ObjectId (24 hex chars) | Token ID |
type | string | Enum: mint, transfer, burn, reward, redeem, expire | Transaction type |
amount | string | Numeric string for precision. > "0" | Transaction amount |
fromUserId | string | MongoDB ObjectId (24 hex chars). null for mint operations | Sender user ID |
toUserId | string | MongoDB ObjectId (24 hex chars). null for burn operations | Recipient user ID |
memo | string | null if not provided. No max length | Transaction memo/reason |
createdAt | string | ISO 8601 datetime. Auto-generated | Transaction timestamp |
Pagination
| Field | Type | Constraints / Format | Description |
|---|---|---|---|
page | integer | >= 1 | Current page number (1-indexed) |
limit | integer | 1 - 100 | Items per page |
total | integer | >= 0 | Total items matching the query |
totalPages | integer | >= 0 | Total pages available |
hasNextPage | boolean | true or false | Whether more pages exist after this |
hasPrevPage | boolean | true or false | Whether pages exist before this |
12. Code Examples
JavaScript/Node.js Example
// VIO API Client Example
const VIO_API_BASE = "https://your-domain.com/api/external/v1";
const API_KEY = "vio_live_your_api_key_here";
// Helper function for API calls
async function vioApi(method, endpoint, body = null) {
const options = {
method,
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${VIO_API_BASE}${endpoint}`, options);
const data = await response.json();
if (!data.success) {
throw new Error(data.error?.message || "API request failed");
}
return data;
}
// Example: Create a voucher
async function createVoucher() {
const result = await vioApi("POST", "/vouchers", {
name: "Welcome Discount",
description: "10% off your first purchase",
value: 10,
valueType: "percentage",
totalQuantity: 1000,
maxClaimsPerUser: 1,
visibility: "public",
endDate: "2024-12-31T23:59:59.000Z",
});
console.log("Created voucher:", result.data._id);
return result.data;
}
// Example: Mint tokens to a user
async function mintTokensToUser(tokenId, userId, amount) {
const result = await vioApi("POST", `/tokens/${tokenId}/mint`, {
toUserId: userId,
amount: amount.toString(),
memo: "Reward for purchase",
});
console.log("Minted tokens:", result.data);
return result.data;
}
// Example: Redeem a voucher by code
async function redeemVoucher(redemptionCode, location) {
const result = await vioApi("POST", "/vouchers/redeem-by-code", {
redemptionCode,
location,
notes: "Redeemed at checkout",
});
console.log("Voucher redeemed:", result.data);
return result.data;
}
// Example: Consume a voucher with staff PIN
async function consumeVoucherByPin(redemptionCode, staffPin) {
const result = await vioApi(
"POST",
`/vouchers/redeem/${redemptionCode}/pin`,
{
pin: staffPin, // e.g., 'HA1234'
},
);
console.log("Voucher consumed by:", result.data.redeemedBy);
return result.data;
}
// Example: Get user with their balances
async function getUserWithBalances(userId) {
const [userResult, balancesResult] = await Promise.all([
vioApi("GET", `/users/${userId}`),
vioApi("GET", `/users/${userId}/balances`),
]);
return {
user: userResult.data,
balances: balancesResult.data,
};
}
// Example: Search for a user by email
async function findUserByEmail(email) {
const result = await vioApi(
"GET",
`/users/search/by-identifier?email=${encodeURIComponent(email)}`,
);
return result.data;
}Python Example
import requests
from typing import Optional, Dict, Any
VIO_API_BASE = 'https://your-domain.com/api/external/v1'
API_KEY = 'vio_live_your_api_key_here'
def vio_api(method: str, endpoint: str, body: Optional[Dict] = None) -> Dict[str, Any]:
"""Make a request to the VIO API."""
headers = {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
}
url = f'{VIO_API_BASE}{endpoint}'
if method == 'GET':
response = requests.get(url, headers=headers)
elif method == 'POST':
response = requests.post(url, headers=headers, json=body)
elif method == 'PATCH':
response = requests.patch(url, headers=headers, json=body)
elif method == 'DELETE':
response = requests.delete(url, headers=headers)
else:
raise ValueError(f'Unsupported method: {method}')
data = response.json()
if not data.get('success'):
error = data.get('error', {})
raise Exception(error.get('message', 'API request failed'))
return data
# Example: List active vouchers
def list_vouchers(page: int = 1, limit: int = 20):
result = vio_api('GET', f'/vouchers?page={page}&limit={limit}&isActive=true')
return result['data'], result['pagination']
# Example: Create a user
def create_user(email: str, password: str, display_name: str):
result = vio_api('POST', '/users', {
'email': email,
'password': password,
'displayName': display_name,
'role': 'member',
})
return result['data']
# Example: Get analytics overview
def get_analytics_overview():
result = vio_api('GET', '/analytics/overview')
return result['data']
# Usage
if __name__ == '__main__':
# List vouchers
vouchers, pagination = list_vouchers()
print(f'Found {pagination["total"]} vouchers')
# Get analytics
analytics = get_analytics_overview()
print(f'Total users: {analytics["users"]["total"]}')Complete Workflow: Create Voucher and Track Claims
#!/bin/bash
# Complete workflow example using cURL
API_BASE="https://your-domain.com/api/external/v1"
API_KEY="vio_live_your_api_key_here"
# 1. Create a new voucher
echo "Creating voucher..."
VOUCHER_RESPONSE=$(curl -s -X POST "$API_BASE/vouchers" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Flash Sale 25% Off",
"description": "Limited time offer - 25% discount",
"value": 25,
"valueType": "percentage",
"totalQuantity": 100,
"maxClaimsPerUser": 1,
"visibility": "public",
"startDate": "2024-02-01T00:00:00.000Z",
"endDate": "2024-02-28T23:59:59.000Z"
}')
VOUCHER_ID=$(echo $VOUCHER_RESPONSE | jq -r '.data._id')
echo "Created voucher: $VOUCHER_ID"
# 2. Check voucher details
echo "Fetching voucher details..."
curl -s -X GET "$API_BASE/vouchers/$VOUCHER_ID" \
-H "X-API-Key: $API_KEY" | jq
# 3. List claims for this voucher
echo "Listing claims..."
curl -s -X GET "$API_BASE/vouchers/claims/list?voucherId=$VOUCHER_ID" \
-H "X-API-Key: $API_KEY" | jq
# 4. When a customer presents a redemption code
REDEMPTION_CODE="ABC123XYZ"
echo "Looking up redemption code..."
curl -s -X GET "$API_BASE/vouchers/redeem/$REDEMPTION_CODE/info" \
-H "X-API-Key: $API_KEY" | jq
# 5. Redeem the voucher
echo "Redeeming voucher..."
curl -s -X POST "$API_BASE/vouchers/redeem-by-code" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"redemptionCode\": \"$REDEMPTION_CODE\",
\"location\": \"Main Store\",
\"notes\": \"Customer order #12345\"
}" | jq
# 6. Check analytics
echo "Fetching voucher analytics..."
curl -s -X GET "$API_BASE/analytics/vouchers" \
-H "X-API-Key: $API_KEY" | jq13. Changelog
Version 1.1.0 (April 2026)
Registration Source, Campaign Analytics & PIN Permissions
- Added
registrationSourcefield to User model (created,direct,store,campaign) - Added
storeIdandcampaignIdpopulated references in User responses - Added campaign visit and registration tracking analytics
- New public endpoint:
POST /api/campaigns/:id/track-visitfor campaign page visit tracking - Campaign list now includes
analyticsobject withvisitsandregistrationscounts - Added
permissionsfield to redemption PINs (voucher_redemption,token_claim) - Each PIN can now have one or more permissions controlling what operations it can authorize
POST /api/public/verify-pinresponse now includes apermissionsarray- Voucher consumption by PIN (
POST /vouchers/redeem/:code/pin) now requires the PIN to have thevoucher_redemptionpermission - Existing PINs without explicit permissions default to
voucher_redemptionfor backward compatibility
Version 1.0.0 (February 2024)
Initial Release
- Full CRUD operations for Vouchers
- Full CRUD operations for Users
- Full CRUD operations for Campaigns
- Token management (list, mint, burn, adjust)
- Analytics endpoints for vouchers, users, and tokens
- API key authentication with scopes
- Rate limiting (60/min, 10,000/day)
- IP whitelisting support
Need Help?
- Admin Portal: Create and manage API keys at Settings > API Keys
- Swagger UI: Interactive API documentation at
/api-docs - Support: Contact VIO Support for assistance
This documentation is for VIO External API v1. Last updated: March 2026.