Skip to content

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/v1

All 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

  1. Log in to the VIO Admin Portal
  2. Navigate to Settings > API Keys
  3. Click Create API Key
  4. Select the scopes (permissions) for the key
  5. Optionally configure IP whitelisting
  6. 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:

bash
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:

ScopeDescriptionEndpoints
vouchersManage vouchers and claims/vouchers/*
usersManage users/users/*
campaignsManage campaigns/campaigns/*
tokensManage tokens and balances/tokens/*
analyticsAccess analytics data/analytics/*

IP Whitelisting

For additional security, you can restrict API key usage to specific IP addresses:

  1. Go to Admin Portal > Settings > API Keys
  2. Edit your API key
  3. Add allowed IP addresses or CIDR ranges
  4. 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 TypeDefault Value
Per Minute60 requests
Per Day10,000 requests

Rate Limit Headers

Every response includes rate limit information in the headers:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the rate limit resets

Example Response Headers

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1699574400

Rate Limit Exceeded

When you exceed the rate limit, you'll receive a 429 Too Many Requests response:

json
{
  "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 429 responses
  • 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/json header for POST/PATCH requests
  • Query parameters are used for filtering and pagination

Success Response

Successful responses follow this structure:

json
{
  "success": true,
  "data": { ... },
  "message": "Optional success message"
}

Paginated Response

List endpoints return paginated data:

json
{
  "success": true,
  "data": [ ... ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 150,
    "totalPages": 8,
    "hasNextPage": true,
    "hasPrevPage": false
  }
}

Pagination Parameters

ParameterTypeDefaultDescription
pageinteger1Page number (1-indexed)
limitinteger20Items per page (max: 100)

Error Response

Error responses follow this structure:

json
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description"
  }
}

5. Error Handling

HTTP Status Codes

Status CodeMeaning
200 OKRequest succeeded
201 CreatedResource created successfully
400 Bad RequestInvalid request parameters or body
401 UnauthorizedMissing or invalid API key
403 ForbiddenAPI key lacks required scope or IP not whitelisted
404 Not FoundResource not found
429 Too Many RequestsRate limit exceeded
500 Internal Server ErrorServer error

Error Codes

Error CodeDescription
VALIDATION_ERRORRequest body or parameters failed validation
UNAUTHORIZEDAPI key is missing or invalid
FORBIDDENAPI key does not have required scope
NOT_FOUNDRequested resource does not exist
RATE_LIMIT_EXCEEDEDToo many requests
ALREADY_EXISTSResource already exists (e.g., duplicate user)
INSUFFICIENT_BALANCENot enough token balance for operation
ALREADY_REDEEMEDVoucher has already been redeemed
EXPIREDVoucher or token has expired
INTERNAL_ERRORUnexpected server error

Example Error Response

json
{
  "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:

ScenarioStep 1 (issue/claim)Step 2 (redeem)When to use
User self-claim from campaignPOST /api/vouchers/:voucherId/claim with campaignIdPOST /api/vouchers/me/:userVoucherId/redeemEnd-user app (JWT)
Tenant/backend directly issues to userPOST /vouchers/:voucherId/send with userIdPOST /vouchers/redeem-by-code or POST /vouchers/redeem/:code/pinAPI 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 consumptionType qr_code or coupon_code with a supplier code pool (CSV uploaded in Admin Portal), redeem assigns the next available code to externalVoucherCode and 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 externalFulfillmentStatus in 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:

  • externalProvider and externalId both present: this is an external voucher.
  • externalProvider and externalId both missing: this is a tenant-managed voucher.
  • consumptionType is usage mode (vio_code, coupon_code, url, qr_code, manual, zhichong), not source-of-truth for tenant/external.
  • voucherType is business category, not source-of-truth for tenant/external.

Where to read decision fields

What you need to decideRead from APIKey fields
Is this voucher external or tenant-managed?GET /vouchers or GET /vouchers/:voucherIdexternalProvider, externalId
Which redeem input style is needed?GET /vouchers or GET /vouchers/:voucherIdconsumptionType, externalRequiresDirectOrderParams
Which claim instance should be redeemed?Claim/send response or user-claim listuserVoucherId (_id), redemptionCode, status
External fulfillment outputs after redeemRedeem responseexternalVoucherCode, 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.

bash
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):

json
{
  "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.

bash
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):

json
{
  "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:

bash
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):

json
{
  "success": true,
  "data": {
    "_id": "507f1f77bcf86cd799439050",
    "status": "redeemed",
    "externalFulfillmentStatus": "fulfilled",
    "externalIsDirectRecharge": true,
    "externalRechargeAccount": "12312332123",
    "externalOrderId": "69f75fe31134f32e472b2f98"
  },
  "message": "Voucher redeemed"
}

Voucher Source & Type Matrix

Voucher sourceexternalProviderexternalIdTypical consumptionTypeRecommended claim/sendRecommended redeem
Tenant-managed vouchermissingmissingvio_code, coupon_code, url, qr_code, manualPOST /vouchers/:voucherId/send (API Key) or member claim flowPOST /vouchers/redeem-by-code, POST /vouchers/redeem/:code/pin, or member redeem
External voucher (code/url style)presentpresentusually coupon_code or urlClaim/send firstRedeem after claim/send
External voucher (zhichong)presentpresentzhichongClaim/send firstPOST /api/vouchers/me/:userVoucherId/redeem with providerParams.account

POST /api/external/v1/vouchers/redeem-by-code does not accept providerParams. POST /api/external/v1/vouchers/redeem/:code/pin cannot be used for zhichong.

Error handling & fulfillment status

Common claim/send errors

HTTP StatusError CodeCause
404NOT_FOUNDvoucherId or userId does not exist
400VALIDATION_ERRORVoucher is not active or has expired
400VALIDATION_ERRORVoucher is sold out (claimedQuantity >= totalQuantity)
400VALIDATION_ERRORUser has reached maxClaimsPerUser limit

Common redeem errors

HTTP StatusError CodeCause
404NOT_FOUNDredemptionCode does not match any active claim
400VALIDATION_ERRORVoucher claim is already redeemed (ALREADY_REDEEMED)
400VALIDATION_ERRORVoucher claim has expired (EXPIRED)
400VALIDATION_ERRORzhichong voucher cannot use PIN redeem

External voucher fulfillment status

After redeeming an external voucher, inspect externalFulfillmentStatus in the response:

externalFulfillmentStatusMeaningAction required
fulfilledProvider purchase succeeded. externalVoucherCode or externalRedemptionUrl is available (for non-zhichong)Deliver the code/url to the user
failedProvider purchase failedYou may retry by calling redeem again on the same claim. Check externalFulfillmentError for details
processingProvider 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:

json
{
  "_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 /vouchers

Query Parameters:

ParameterTypeRequiredConstraintsDescription
pageintegerNoMin: 1. Default: 1Page number (1-indexed)
limitintegerNoMin: 1, Max: 100. Default: 20Items per page
visibilitystringNoEnum: private, public, sharedFilter by visibility scope
isActivebooleanNoString "true" or "false" (parsed as boolean)Filter by active status
categorystringNoFree-form stringFilter by category tag
searchstringNoFree-form stringSearch by voucher name (partial match)
subCompanyIdstringNoMongoDB ObjectId (24 hex chars)Filter by sub-company
excludeTenantManualCreatedbooleanNoString "true" or "false". Default: falseWhen true, exclude vouchers manually created by the current tenant while keeping externally supplied vouchers such as vouchain

Example Request:

bash
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:

json
{
  "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):

FieldTypeNullableConstraints / FormatDescription
_idstringNoMongoDB ObjectId (24 hex chars)Unique identifier for the voucher
namestringNoMin: 1 char. TrimmedDisplay name of the voucher
descriptionstringNoMay be empty string ""Detailed description of the voucher offer
valuenumberNo>= 0. Default: 0Discount value. Interpretation depends on valueType
valueTypestringNoEnum: fixed, percentageHow value is applied: fixed (flat amount) or percentage (discount rate)
valueCurrencystringNoISO 4217 code (e.g., THB, HKD, USD). Default: THBCurrency for value when valueType is fixed
minSpendnumberNo>= 0. Default: 0Minimum spend required before the voucher can be applied. 0 means no minimum
maxDiscountnumberYes> 0; may be null when unsetMaximum discount cap for percentage vouchers
termsstringNoMay be empty string ""Terms and conditions for using the voucher
imagesstring[]NoArray of valid URLs. May be empty []Image URLs. First image is the primary display
isActivebooleanNotrue or falseWhether the voucher is currently active and available for claiming
isTransferablebooleanNotrue or false. Default: falseWhether a claimed voucher can be transferred between users
totalQuantityintegerNo-1 = unlimited; >= 0 = limitedMaximum vouchers available. No more claims when claimedQuantity >= totalQuantity
claimedQuantityintegerNo>= 0Number of vouchers already claimed by users
maxClaimsPerUserintegerNo0 = unlimited; >= 1 = limitedMaximum times a single user can claim this voucher
voucherTypestringNoEnum: cash, discount, product, cash_discount. Default: discountVoucher business category. Do not use for tenant/external source decision
consumptionTypestringNoEnum: vio_code, coupon_code, url, qr_code, manual, zhichongVoucher consumption mode. Use with source fields to choose redeem input style
externalProviderstringYesProvider code or nullExternal source indicator. Together with externalId determines external voucher
externalIdstringYesProvider-side template ID or nullExternal source indicator. Together with externalProvider determines external voucher
externalRequiresDirectOrderParamsbooleanYestrue / false / nullWhether external redeem may require account params (for example direct recharge flow)
externalDataobjectYesObject or nullRaw or extended external provider data. Shape may vary by provider
categorystringYesEnum: Wellness, Health, Food & Beverage, Leisure & Entertainment, Travel & Hospitality, Lifestyle & Services, Others. May be nullPrimary product/service category of the voucher. Use this for category-based grouping or filtering
categoriesstring[]NoArray of trimmed strings. May be []Legacy free-form category tags (deprecated — prefer category). Still returned for backwards compatibility
applicableBrandsobject[]NoEach item includes _id, name, slugApplicable brands/sub-companies. Legacy field; prefer applicableBrandTags
applicableBrandTagsobject[]NoEach item includes _id, name, logoApplicable brand tags
settlementAmountnumberNo>= 0. Default: 0Settlement 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
settlementCurrencystringNoISO 4217 code, uppercase. Default: THBCurrency for settlementAmount
crossTenantReceivableTimingstringNoEnum: redemption, consumption. Default: redemptionWhen to record cross-tenant receivable settlement
startDatestringYesISO 8601 datetime. null if not setWhen the voucher becomes valid
endDatestringYesISO 8601 datetime. null = no expiryWhen the voucher expires
visibilitystringNoEnum: private, public, sharedAccess scope. Also controls whether the voucher can be transferred between users
tenantIdobjectNoIncludes _id, name, slugTenant that owns the voucher
subCompanyIdobjectYesIncludes _id, name, slug; may be nullSub-company that owns the voucher
isOwnbooleanNotrue or falseWhether the voucher belongs to the current request context organization
ownerOrgobjectYes{ "name": string, "type": "tenant/subCompany" }; usually omitted for own vouchersOwner organization info for non-own vouchers
targetTiersstring[]NoArray of strings. May be []Membership tiers that can see or claim this voucher
targetUserGroupsstring[]NoArray of MongoDB ObjectIds. May be []User groups that can see or claim this voucher
applicableScopestringNoEnum: all_outlets, partial_outlets, single_store. Default: all_outletsStore applicability scope
applicableCountrystringYesISO country/region code. May be nullSingle applicable country/region. Legacy field
applicableCountriesstring[]NoArray of ISO country/region codes. May be []Applicable countries/regions
storeIdstringYesMongoDB ObjectId. May be nullStore ID for single-store applicability
storeLocationobjectYesIncludes name, address, latitude, longitude; may be nullEmbedded store location
bookingEnabledbooleanNotrue or false. Default: falseWhether booking is enabled
bookingDaysInAdvanceintegerNo>= 0. Default: 0How many days in advance users can book
consumptionMessagestringNoMay be empty string ""Instructions for manual consumption type
consumptionUrlstringNoMay be empty string ""URL used for url consumption type
consumptionQrCodestringNoMay be empty string ""QR code image path for qr_code consumption type
contractAddressstringYesBlockchain contract address. May be nullVoucher NFT contract address
createdBystringYesMongoDB ObjectId. May be nullAdmin user ID that created/deployed the voucher
metadataobjectNoObject. Default: {}Extended metadata
createdAtstringNoISO 8601 datetimeWhen the voucher was created
updatedAtstringNoISO 8601 datetimeWhen the voucher was last modified

Create Voucher

Create a new voucher for your tenant.

POST /vouchers

Request Body:

FieldTypeRequiredConstraintsDescription
namestringYesMin: 1 character. TrimmedVoucher display name shown to users in the member app
descriptionstringNoNo max length. Default: ""Detailed description of the voucher offer. Plain text only
voucherTypestringNoEnum: cash, discount, product, cash_discount. Default: discountBusiness category of the voucher
visibilitystringNoEnum: private, public, shared. Default: privateAccess scope: private (own tenant only), public (all tenants), shared (specific tenants in sharedWithTenants)
sharedWithTenantsstring[]NoEach element: MongoDB ObjectId (24 hex chars)Tenant IDs to share with. Only applicable when visibility is shared
sharedWithUsersstring[]NoEach element: MongoDB ObjectId (24 hex chars)User IDs who can see this voucher. Used for targeted distribution
termsstringNoNo max length. Default: ""Terms and conditions displayed to users before claim/redeem
valuenumberNoMin: 0. Default: 0Discount value. When valueType is fixed, this is the flat discount amount. When percentage, this is the rate (e.g., 20 = 20% off)
valueTypestringNoEnum: fixed, percentage. Default: fixedHow value is interpreted: fixed (flat currency amount) or percentage (discount rate)
valueCurrencystringNoISO 4217 code (e.g., THB, HKD, USD). Default: THBCurrency code for value when valueType is fixed
minSpendnumberNoMin: 0. Default: 0Minimum purchase amount required before voucher can be applied. 0 = no minimum
maxDiscountnumberNoMust be > 0 (positive only)Maximum discount cap. Useful for percentage vouchers (e.g., 20% off but max $100 discount)
totalQuantityintegerNoInteger only. -1 = unlimited. Default: -1Total vouchers available for claiming. No more claims when fully claimed
startDatestringNoISO 8601 datetime (e.g., 2024-06-01T00:00:00.000Z). Default: current timeWhen the voucher becomes claimable. Omit for immediately available
endDatestringNoISO 8601 datetime. Must be after startDateWhen the voucher expires. Claims/redemptions after this are rejected. Omit for no expiry
maxClaimsPerUserintegerNoInteger only. Min: 0. 0 = unlimited. Default: 1Maximum times a single user can claim this voucher
settlementAmountnumberNoMin: 0. Default: 0Fiat amount for inter-tenant settlement when redeemed in cross-tenant scenario
settlementCurrencystringNoISO 4217 code. Stored uppercase. Trimmed. Default: THBCurrency for settlementAmount. Defaults to tenant's primary currency from Billing Settings. All settlement records stored in this currency
categorystringNoEnum: Wellness, Health, Food & Beverage, Leisure & Entertainment, Travel & Hospitality, Lifestyle & Services, OthersPrimary voucher category. Prefer this field for categorization, for example "Others"
categoriesstring[]NoArray of strings. Each element trimmedCategory tags for organizing vouchers (e.g., ["food", "lifestyle"])
imagesstring[]NoEach element must be a valid URLImage URLs. First image is used as primary display
targetTiersstring[]NoArray of strings. Each element trimmedMembership tier names. Only users in these tiers can see/claim
targetUserGroupsstring[]NoEach element: MongoDB ObjectId (24 hex chars)User group IDs. Only users in these groups can see/claim
subCompanyIdstringNoMongoDB ObjectId (24 hex chars)Sub-company to scope this voucher to

Example Request:

bash
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:

json
{
  "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/:voucherId

Path Parameters:

ParameterTypeRequiredDescription
voucherIdstringYesVoucher ID

Example Request:

bash
curl -X GET "https://your-domain.com/api/external/v1/vouchers/507f1f77bcf86cd799439011" \
  -H "X-API-Key: vio_live_your_api_key_here"

Example Response:

json
{
  "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/:voucherId

Path Parameters:

ParameterTypeRequiredDescription
voucherIdstringYesVoucher ID

Request Body:

All fields are optional. Include only the fields you want to update.

FieldTypeConstraintsDescription
namestringMin: 1 characterVoucher display name
descriptionstringNo max lengthVoucher description
voucherTypestringEnum: cash, discount, product, cash_discountBusiness category of the voucher
visibilitystringEnum: private, public, sharedAccess scope
sharedWithTenantsstring[]Each element: MongoDB ObjectId (24 hex chars)Tenant IDs to share with (only for shared visibility)
sharedWithUsersstring[]Each element: MongoDB ObjectId (24 hex chars)User IDs for targeted distribution
termsstringNo max lengthTerms and conditions
valuenumberMin: 0Discount value
valueTypestringEnum: fixed, percentageHow value is interpreted
valueCurrencystringISO 4217 code (e.g., THB, HKD, USD)Currency for fixed-value vouchers
minSpendnumberMin: 0Minimum purchase amount. 0 = no minimum
maxDiscountnumberMust be > 0 (positive only)Maximum discount cap for percentage vouchers
totalQuantityintegerInteger only. -1 = unlimitedTotal available quantity
startDatestringISO 8601 datetimeWhen voucher becomes valid
endDatestringISO 8601 datetimeWhen voucher expires
maxClaimsPerUserintegerInteger only. Min: 0. 0 = unlimitedMax claims per user
settlementAmountnumberMin: 0Settlement amount for cross-tenant reconciliation
settlementCurrencystringISO 4217 code. Stored uppercase. TrimmedSettlement currency. Defaults to tenant's primary currency. All records stored in tenant's currency
categorystringEnum: Wellness, Health, Food & Beverage, Leisure & Entertainment, Travel & Hospitality, Lifestyle & Services, OthersPrimary voucher category, for example "Others"
isActivebooleantrue or falseActive status
categoriesstring[]Array of strings. Each element trimmedCategory tags
imagesstring[]Each element must be a valid URLImage URLs
targetTiersstring[]Array of strings. Each element trimmedMembership tier names for targeting
targetUserGroupsstring[]Each element: MongoDB ObjectId (24 hex chars)User group IDs for targeting

Example Request:

bash
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:

json
{
  "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/:voucherId

Path Parameters:

ParameterTypeRequiredDescription
voucherIdstringYesVoucher ID

Example Request:

bash
curl -X DELETE "https://your-domain.com/api/external/v1/vouchers/507f1f77bcf86cd799439011" \
  -H "X-API-Key: vio_live_your_api_key_here"

Example Response:

json
{
  "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/duplicate

Path Parameters:

ParameterTypeRequiredDescription
voucherIdstringYesVoucher ID to duplicate

Example Request:

bash
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:

json
{
  "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/send

Path Parameters:

ParameterTypeRequiredDescription
voucherIdstringYesVoucher ID to send

Request Body:

FieldTypeRequiredConstraintsDescription
userIdstringYesMin: 1 character. MongoDB ObjectId (24 hex chars)User ID to send the voucher to

Example Request:

bash
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:

json
{
  "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:

FieldTypeNullableConstraints / FormatDescription
_idstringNoMongoDB ObjectId (24 hex chars)Unique identifier for this voucher claim (UserVoucher record)
userIdstringNoMongoDB ObjectId (24 hex chars)The user who received the voucher
voucherIdstringNoMongoDB ObjectId (24 hex chars)The voucher template ID that was sent
tenantIdstringNoMongoDB ObjectId (24 hex chars)The tenant that owns this voucher claim
nftTokenIdstringYesNumeric string (blockchain token ID). null if minting is pendingOn-chain NFT token ID minted for this claim
statusstringNoEnum: active, claimed, redeemed, expired. Always active for newly sentCurrent claim status
redemptionCodestringNoFormat: VCH-{base36_timestamp}-{4_random_chars} (e.g., VCH-M1ABC2-XY3Z). UniqueCode the user presents to redeem the voucher
expiresAtstringYesISO 8601 datetime. null = no expiryWhen this claim expires. Inherited from voucher's endDate at claim time
settlementAmountnumberNo>= 0. Default: 0Settlement amount copied from voucher at claim time for cross-tenant reconciliation
settlementCurrencystringNoISO 4217 code, uppercase (e.g., HKD, THB). Default: THBCurrency for settlementAmount
claimedAtstringNoISO 8601 datetimeWhen the voucher was sent to the user
createdAtstringNoISO 8601 datetimeRecord creation timestamp

Error Responses:

StatusCodeDescription
400VALIDATION_ERRORVoucher is not active, expired, sold out, or user at claim limit
404NOT_FOUNDVoucher or user not found

List Voucher Claims

Get a list of all voucher claims for your tenant.

GET /vouchers/claims/list

Query Parameters:

ParameterTypeRequiredConstraintsDescription
pagestringNoParsed as integer. Min: 1. Default: 1Page number (1-indexed)
limitstringNoParsed as integer. Min: 1. Default: 20Items per page
voucherIdstringNoMongoDB ObjectId (24 hex chars)Filter by voucher ID
statusstringNoEnum: active, claimed, redeemed, expiredFilter by claim status
fromDatestringNoISO 8601 datetime (e.g., 2024-01-01T00:00:00.000Z)Filter claims from this date (inclusive)
toDatestringNoISO 8601 datetimeFilter claims until this date (inclusive)
searchstringNoFree-form stringSearch by user name or email (partial match)

Example Request:

bash
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:

json
{
  "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):

FieldTypeNullableConstraints / FormatDescription
_idstringNoMongoDB ObjectId (24 hex chars)Unique claim identifier
voucherIdobjectNoPopulated voucher summaryAssociated voucher template
voucherId._idstringNoMongoDB ObjectId (24 hex chars)Voucher template ID
voucherId.namestringNoTrimmedVoucher display name
voucherId.imagesstring[]NoArray of URLs. May be []Voucher images
voucherId.valuenumberNo>= 0Voucher value
voucherId.valueTypestringNoEnum: fixed, percentageHow value is applied
userIdobjectNoPopulated user summaryUser who received or claimed the voucher
userId._idstringNoMongoDB ObjectId (24 hex chars)User ID
userId.displayNamestringYesMay be null if not setUser display name
userId.emailstringYesEmail formatUser email
userId.avatarstringYesURL or nullUser avatar
tenantIdstringNoMongoDB ObjectId (24 hex chars)Tenant that owns the claim record
voucherOwnerTenantIdstringYesMongoDB ObjectId (24 hex chars)Tenant that owns the voucher template
nftTokenIdstringYesBlockchain token ID. May be nullNFT token minted for this claim
statusstringNoEnum: active, claimed, redeemed, expiredCurrent claim status
redemptionCodestringNoFormat: VCH-{base36_timestamp}-{4_random_chars}. UniqueCode for redeeming the voucher
claimedAtstringNoISO 8601 datetimeWhen the voucher was claimed or sent
redeemedAtstringYesISO 8601 datetime. May be nullWhen the voucher was redeemed
expiresAtstringYesISO 8601 datetime. null = no expiryWhen this claim expires
campaignIdstringYesMongoDB ObjectId. May be nullCampaign associated with the claim, if any
settlementAmountnumberNo>= 0. Default: 0Settlement amount captured at claim time
settlementCurrencystringNoISO 4217 codeSettlement currency
tokenAmountPaidstringNoNumeric string. Default: "0"Token amount paid at claim time
redemptionDetailsobjectYesObject or nullRedemption metadata after use
externalVoucherCodestringYesString or nullFulfilled external voucher code
externalRedemptionUrlstringYesURL or nullFulfilled external redemption URL
externalOrderIdstringYesProvider order ID or nullExternal provider order ID
externalBuyStatusstringYesEnum: pending, recorded, failed, nullExternal buy status
externalFulfillmentStatusstringYesEnum: pending, processing, fulfilled, failed, nullExternal fulfillment status
externalFulfillmentErrorstringYesString or nullExternal fulfillment error details
externalIsDirectRechargebooleanNotrue or false. Default: falseWhether this fulfillment was direct recharge
externalRechargeAccountstringYesString or nullDirect recharge account, if applicable
metadataobjectNoObject. Default: {}Extended metadata
createdAtstringNoISO 8601 datetimeRecord creation timestamp
updatedAtstringNoISO 8601 datetimeLast 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-code

Request Body:

FieldTypeRequiredConstraintsDescription
redemptionCodestringYesMin: 1 character. Case-sensitiveThe unique redemption code from the voucher claim (e.g., VCH-M1ABC2-XY3Z)
locationstringNoNo max lengthWhere the redemption occurred (e.g., store name or branch)
notesstringNoNo max lengthAdditional notes about the redemption (e.g., order number)

Example Request:

bash
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:

json
{
  "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:

FieldTypeNullableConstraints / FormatDescription
_idstringNoMongoDB ObjectId (24 hex chars)Claim identifier
voucherIdstring or objectNoMongoDB ObjectId or populated voucher objectAssociated voucher template
userIdstringNoMongoDB ObjectId (24 hex chars)User who owned the voucher
statusstringNoredeemed after successful redemptionClaim status
redemptionCodestringNoFormat: VCH-{base36_timestamp}-{4_random_chars}The redeemed code
claimedAtstringNoISO 8601 datetimeWhen the voucher was originally claimed
redeemedAtstringNoISO 8601 datetimeWhen the voucher was redeemed
redemptionDetails.locationstringYesMay be omitted if not provided in requestWhere the redemption occurred
redemptionDetails.notesstringYesMay be omitted if not provided in requestAdditional redemption notes
redemptionDetails.methodstringNoapi_keyRedemption method
redemptionDetails.redeemedBystringNoapi:{apiKeyId}API key actor recorded for the redemption
redemptionDetails.redeemedByTypestringNoapi_keyActor type
redemptionDetails.redeemedByApiKeystringYesMongoDB ObjectIdAPI key ID, when available
externalVoucherCodestringYesString or nullFulfilled external voucher code
externalRedemptionUrlstringYesURL or nullFulfilled external redemption URL
externalOrderIdstringYesProvider order ID or nullExternal provider order ID
externalFulfillmentStatusstringYesEnum: fulfilled, failed, processing, pending, nullExternal 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/info

Path Parameters:

ParameterTypeRequiredDescription
codestringYesRedemption code

Example Request:

bash
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:

json
{
  "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:

FieldTypeNullableConstraints / FormatDescription
redemptionCodestringNoRedemption codeThe code that was looked up
statusstringNoEnum: active, claimed, redeemed, expiredDisplay status. Expired active claims return expired
voucherobjectYesObject or nullVoucher display summary
voucher.namestringNoTrimmedVoucher display name
voucher.descriptionstringNoMay be empty ""Voucher description
voucher.valuenumberNo>= 0Discount value
voucher.valueTypestringNoEnum: fixed, percentageHow value is applied
voucher.valueCurrencystringNoISO 4217 codeCurrency for fixed-value vouchers
voucher.imagestringYesURL or nullFirst voucher image
voucher.termsstringNoMay be empty ""Terms and conditions
tenantobjectYesObject or nullVoucher owner tenant summary
tenant.namestringNoTextTenant name
tenant.logostringYesURL or nullTenant logo
tenant.slugstringYesSlug or nullTenant slug
pinPrefixstringNoText, e.g. VIPIN prefix expected for staff PIN redemption
expiresAtstringYesISO 8601 datetime. null = no expiryWhen this claim expires
canRedeembooleanNotrue or falseWhether 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_redemption permission to consume vouchers. PINs that only have the token_claim permission 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/pin

Path Parameters:

ParameterTypeRequiredDescription
codestringYesThe redemption code from the voucher claim

Request Body:

FieldTypeRequiredConstraintsDescription
pinstringYesMin: 5 characters. Max: 20 characters. Typical format: 2-letter prefix + 4-digit PIN (e.g., HA1234). Must have voucher_redemption permissionStaff PIN for identity verification at point of consumption

Example Request:

bash
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:

json
{
  "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:

FieldTypeNullableConstraints / FormatDescription
successbooleanNoAlways true on successConsumption result
voucher.namestringNoTrimmedName of the consumed voucher
voucher.valuenumberNo>= 0Discount value
voucher.valueTypestringNoEnum: fixed, percentageHow value is applied
voucher.valueCurrencystringNoISO 4217 code (e.g., THB, HKD)Currency for fixed-value vouchers
redeemedAtstringNoISO 8601 datetimeWhen the voucher was consumed
redeemedBystringNoStaff member's display nameName of the staff who verified the PIN

Error Responses:

StatusCodeDescription
400VALIDATION_ERRORInvalid PIN format (must be 5-20 characters)
400VALIDATION_ERRORInvalid PIN (wrong prefix or PIN doesn't match any staff)
400VALIDATION_ERRORPIN lacks voucher_redemption permission
400VALIDATION_ERRORVoucher is already redeemed or expired
404NOT_FOUNDVoucher 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 /users

Query Parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default: 1)
limitintegerNoItems per page (default: 20)
rolestringNoFilter by role: super_admin, tenant_admin, sub_company_admin, member
searchstringNoSearch by email, phone, or name
isActivebooleanNoFilter by active status
subCompanyIdstringNoFilter by sub-company

Example Request:

bash
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:

json
{
  "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 /users

Request Body:

FieldTypeRequiredDescription
emailstringNo*User email address
phonestringNo*User phone number
passwordstringYesPassword (min 6 characters)
displayNamestringNoUser display name
rolestringNomember or sub_company_admin (default: member)
subCompanyIdstringNoSub-company ID to assign user to
metadataobjectNoCustom metadata key-value pairs

*At least one of email or phone is required.

Example Request:

bash
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:

json
{
  "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/:userId

Path Parameters:

ParameterTypeRequiredDescription
userIdstringYesUser ID

Example Request:

bash
curl -X GET "https://your-domain.com/api/external/v1/users/507f1f77bcf86cd799439015" \
  -H "X-API-Key: vio_live_your_api_key_here"

Example Response:

json
{
  "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/:userId

Path Parameters:

ParameterTypeRequiredDescription
userIdstringYesUser ID

Request Body:

FieldTypeDescription
displayNamestringUser display name
avatarstringAvatar URL
isActivebooleanActive status
subCompanyIdstringSub-company ID
metadataobjectCustom metadata

Example Request:

bash
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:

json
{
  "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/:userId

Path Parameters:

ParameterTypeRequiredDescription
userIdstringYesUser ID

Example Request:

bash
curl -X DELETE "https://your-domain.com/api/external/v1/users/507f1f77bcf86cd799439015" \
  -H "X-API-Key: vio_live_your_api_key_here"

Example Response:

json
{
  "success": true,
  "data": null,
  "message": "User deactivated"
}

Get User Token Balances

Retrieve all token balances for a specific user.

GET /users/:userId/balances

Path Parameters:

ParameterTypeRequiredDescription
userIdstringYesUser ID

Example Request:

bash
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:

json
{
  "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/vouchers

Path Parameters:

ParameterTypeRequiredDescription
userIdstringYesUser ID

Query Parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default: 1)
limitintegerNoItems per page (default: 20)
statusstringNoFilter by status: active, redeemed, expired

Example Request:

bash
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:

json
{
  "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-identifier

Query Parameters:

ParameterTypeRequiredDescription
emailstringNo*User email address
phonestringNo*User phone number
walletAddressstringNo*User wallet address

*At least one identifier is required.

Example Request:

bash
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:

json
{
  "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 /campaigns

Query Parameters:

ParameterTypeRequiredConstraintsDescription
pagestringNoParsed as integer. Min: 1. Default: 1Page number (1-indexed)
limitstringNoParsed as integer. Min: 1. Default: 20Items per page
searchstringNoFree-form stringSearch by campaign name (partial match)
isActivestringNoString "true" or "false" (parsed as boolean)Filter by active status

Example Request:

bash
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:

json
{
  "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):

FieldTypeNullableConstraints / FormatDescription
_idstringNoMongoDB ObjectId (24 hex chars)Unique identifier for the campaign
namestringNoMin: 1 char. TrimmedCampaign display name
descriptionstringNoMay be empty ""Campaign description text
slugstringNoLowercase, URL-safe. Unique per tenant + sub-companyURL-friendly identifier for campaign pages (e.g., summer-promotion)
tokenIdstringNoMongoDB ObjectId (24 hex chars)Token users spend to claim vouchers in this campaign
isActivebooleanNotrue or falseWhether the campaign is currently active
isPublicbooleanNotrue or falseWhether the campaign is publicly accessible without authentication
startDatestringYesISO 8601 datetime. Default: creation timeWhen the campaign starts
endDatestringYesISO 8601 datetime. null = no end dateWhen the campaign ends
imagesstring[]NoArray of valid URLs. May be empty []Campaign banner/cover images
createdAtstringNoISO 8601 datetimeCreation timestamp

Create Campaign

Create a new voucher campaign.

POST /campaigns

Request Body:

FieldTypeRequiredConstraintsDescription
namestringYesMin: 1 character. TrimmedCampaign display name. Used to auto-generate slug if not provided
descriptionstringNoNo max length. Default: ""Campaign description text
slugstringNoLowercase, URL-safe characters only. Unique per tenant + sub-company. Auto-generated from name via slugify if omittedURL-friendly identifier used in campaign page URLs (e.g., summer-promotion-2024)
tokenIdstringYesMin: 1 character. MongoDB ObjectId (24 hex chars). Must reference an existing TokenToken that users spend to claim vouchers in this campaign
startDatestringNoISO 8601 datetime (e.g., 2024-06-01T00:00:00.000Z). Default: current timeWhen the campaign starts accepting claims
endDatestringNoISO 8601 datetime. Should be after startDateWhen the campaign ends. Omit for no end date
isActivebooleanNoDefault: trueWhether the campaign is active. Inactive campaigns are hidden from users
isPublicbooleanNoDefault: trueWhether the campaign appears on public pages (accessible without login)
imagesstring[]NoEach element must be a valid URL. May be empty []Campaign banner/cover image URLs. First image is used as primary display

Example Request:

bash
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:

json
{
  "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/:campaignId

Path Parameters:

ParameterTypeRequiredDescription
campaignIdstringYesCampaign ID

Example Request:

bash
curl -X GET "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030" \
  -H "X-API-Key: vio_live_your_api_key_here"

Example Response:

json
{
  "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/:campaignId

Path Parameters:

ParameterTypeRequiredDescription
campaignIdstringYesCampaign ID

Request Body:

FieldTypeConstraintsDescription
namestringMin: 1 character. TrimmedCampaign display name
descriptionstringNo max lengthCampaign description
tokenIdstringMongoDB ObjectId (24 hex chars). Must reference existing TokenToken for the campaign
startDatestringISO 8601 datetimeCampaign start date
endDatestringISO 8601 datetimeCampaign end date
isActivebooleantrue or falseActive status
isPublicbooleantrue or falsePublic visibility
imagesstring[]Each element must be a valid URLCampaign image URLs

Example Request:

bash
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:

json
{
  "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/:campaignId

Path Parameters:

ParameterTypeRequiredDescription
campaignIdstringYesCampaign ID

Example Request:

bash
curl -X DELETE "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030" \
  -H "X-API-Key: vio_live_your_api_key_here"

Example Response:

json
{
  "success": true,
  "data": {
    "_id": "507f1f77bcf86cd799439030"
  },
  "message": "Campaign deleted"
}

Response Fields:

FieldTypeConstraints / FormatDescription
_idstringMongoDB ObjectId (24 hex chars)ID of the deleted campaign

Get Campaign Vouchers

Retrieve vouchers associated with a campaign.

GET /campaigns/:campaignId/vouchers

Path Parameters:

ParameterTypeRequiredDescription
campaignIdstringYesCampaign ID

Query Parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default: 1)
limitintegerNoItems per page (default: 20)

Example Request:

bash
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:

json
{
  "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):

FieldTypeNullableConstraints / FormatDescription
_idstringNoMongoDB ObjectId (24 hex chars)Voucher template ID
namestringNoTrimmedVoucher display name
valuenumberNo>= 0Discount value (see valueType)
valueTypestringNoEnum: fixed, percentageHow value is applied
tokenAmountstringNoStored as string for precision. "0" = no campaign-level capCampaign stock/quantity cap for this voucher
sortOrderintegerNoDefault: 0. Lower = firstDisplay order within the campaign
isActivebooleanNotrue or falseWhether this voucher is currently claimable within the campaign

Add Vouchers to Campaign

Add vouchers to a campaign with token pricing.

POST /campaigns/:campaignId/vouchers

Path Parameters:

ParameterTypeRequiredDescription
campaignIdstringYesCampaign ID

Request Body:

FieldTypeRequiredConstraintsDescription
voucherIdsstring[]YesMin: 1 element. Each: MongoDB ObjectId (24 hex chars). Duplicates in same campaign are rejectedArray of voucher IDs to add to the campaign
tokenAmountstringYesMin: 1 character. Stored as string for precision. "0" = no campaign-level capCampaign stock/quantity cap for these vouchers

Example Request:

bash
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:

json
{
  "success": true,
  "data": {
    "added": 2
  },
  "message": "Vouchers added to campaign"
}

Response Fields:

FieldTypeConstraints / FormatDescription
addedinteger>= 0Number of vouchers successfully added to campaign

Update Campaign Voucher

Update a voucher's settings within a campaign.

PATCH /campaigns/:campaignId/vouchers/:voucherId

Path Parameters:

ParameterTypeRequiredDescription
campaignIdstringYesCampaign ID
voucherIdstringYesVoucher ID

Request Body:

FieldTypeConstraintsDescription
tokenAmountstringStored as string for precision. "0" = no campaign-level capCampaign stock/quantity cap for this voucher
sortOrderintegerNumber type (can be negative). Default: 0. Lower = firstDisplay order within the campaign
isActivebooleantrue or falseWhether this voucher is claimable in the campaign. Can be temporarily disabled

Example Request:

bash
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:

json
{
  "success": true,
  "data": {
    "campaignId": "507f1f77bcf86cd799439030",
    "voucherId": "507f1f77bcf86cd799439011",
    "tokenAmount": "150",
    "sortOrder": 1
  },
  "message": "Campaign voucher updated"
}

Response Fields:

FieldTypeConstraints / FormatDescription
campaignIdstringMongoDB ObjectId (24 hex chars)Campaign ID
voucherIdstringMongoDB ObjectId (24 hex chars)Voucher ID
tokenAmountstringString for precision. "0" = no capUpdated campaign quantity cap
sortOrderintegerLower = firstUpdated display order

Remove Voucher from Campaign

Remove a voucher from a campaign.

DELETE /campaigns/:campaignId/vouchers/:voucherId

Path Parameters:

ParameterTypeRequiredDescription
campaignIdstringYesCampaign ID
voucherIdstringYesVoucher ID

Example Request:

bash
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:

json
{
  "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-vouchers

Path Parameters:

ParameterTypeRequiredDescription
campaignIdstringYesCampaign ID

Query Parameters:

ParameterTypeRequiredConstraintsDescription
pagestringNoParsed as integer. Min: 1. Default: 1Page number (1-indexed)
limitstringNoParsed as integer. Min: 1. Default: 20Items per page
searchstringNoFree-form stringSearch by voucher name (partial match)

Example Request:

bash
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:

json
{
  "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):

FieldTypeNullableConstraints / FormatDescription
_idstringNoMongoDB ObjectId (24 hex chars)Voucher template ID
namestringNoTrimmedVoucher display name
valuenumberNo>= 0Discount value
valueTypestringNoEnum: fixed, percentageHow value is applied
isActivebooleanNotrue or falseWhether the voucher is active

9. API Reference: Tokens

Required Scope: tokens

List Tokens

Retrieve a list of tokens for your tenant.

GET /tokens

Query Parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default: 1)
limitintegerNoItems per page (default: 20)
isActivebooleanNoFilter by active status
subCompanyIdstringNoFilter by sub-company

Example Request:

bash
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:

json
{
  "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/:tokenId

Path Parameters:

ParameterTypeRequiredDescription
tokenIdstringYesToken ID

Example Request:

bash
curl -X GET "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020" \
  -H "X-API-Key: vio_live_your_api_key_here"

Example Response:

json
{
  "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/stats

Path Parameters:

ParameterTypeRequiredDescription
tokenIdstringYesToken ID

Example Request:

bash
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:

json
{
  "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/holders

Path Parameters:

ParameterTypeRequiredDescription
tokenIdstringYesToken ID

Query Parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default: 1)
limitintegerNoItems per page (default: 20)
sortstringNoSort by: balance, name, recent (default: balance)
searchstringNoSearch by user name or email

Example Request:

bash
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:

json
{
  "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/mint

Path Parameters:

ParameterTypeRequiredDescription
tokenIdstringYesToken ID

Request Body:

FieldTypeRequiredDescription
toUserIdstringYesUser ID to receive tokens
amountstringYesAmount to mint
memostringNoTransaction memo

Example Request:

bash
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:

json
{
  "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/burn

Path Parameters:

ParameterTypeRequiredDescription
tokenIdstringYesToken ID

Request Body:

FieldTypeRequiredDescription
fromUserIdstringYesUser ID to burn from
amountstringYesAmount to burn
memostringNoTransaction memo

Example Request:

bash
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:

json
{
  "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/adjust

Path Parameters:

ParameterTypeRequiredDescription
tokenIdstringYesToken ID

Request Body:

FieldTypeRequiredDescription
userIdstringYesUser ID
amountstringYesAmount to adjust
operationstringYessend or recall
reasonstringYesReason for adjustment

Example Request:

bash
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:

json
{
  "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/send

Path Parameters:

ParameterTypeRequiredDescription
tokenIdstringYesToken ID

Request Body:

FieldTypeRequiredDescription
userIdstringYesUser ID to send tokens to
amountstringYesAmount of tokens to send
reasonstringNoReason for sending tokens

Example Request:

bash
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:

json
{
  "success": true,
  "data": {
    "userBalance": "2000",
    "adminBalance": "8000",
    "transaction": {
      "_id": "507f1f77bcf86cd799439043",
      "tokenId": "507f1f77bcf86cd799439020",
      "type": "transfer",
      "amount": "1000"
    }
  },
  "message": "Tokens sent to user"
}

Error Responses:

StatusCodeDescription
400VALIDATION_ERRORInsufficient company balance for sending tokens
404NOT_FOUNDToken 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/recall

Path Parameters:

ParameterTypeRequiredDescription
tokenIdstringYesToken ID

Request Body:

FieldTypeRequiredDescription
userIdstringYesUser ID to recall tokens from
amountstringYesAmount of tokens to recall
reasonstringNoReason for recalling tokens

Example Request:

bash
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:

json
{
  "success": true,
  "data": {
    "userBalance": "1500",
    "adminBalance": "8500",
    "transaction": {
      "_id": "507f1f77bcf86cd799439044",
      "tokenId": "507f1f77bcf86cd799439020",
      "type": "transfer",
      "amount": "500"
    }
  },
  "message": "Tokens recalled from user"
}

Error Responses:

StatusCodeDescription
400VALIDATION_ERRORInsufficient user balance for recalling tokens
404NOT_FOUNDToken or user not found

List Token Transactions

Retrieve a list of token transactions.

GET /tokens/transactions/list

Query Parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default: 1)
limitintegerNoItems per page (default: 20)
tokenIdstringNoFilter by token ID
typestringNoFilter by type: mint, transfer, burn, reward, redeem
fromDatedatetimeNoFilter from this date
toDatedatetimeNoFilter until this date
searchstringNoSearch by user name or memo

Example Request:

bash
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:

json
{
  "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/:userId

Path Parameters:

ParameterTypeRequiredDescription
userIdstringYesUser ID

Example Request:

bash
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:

json
{
  "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/overview

Example Request:

bash
curl -X GET "https://your-domain.com/api/external/v1/analytics/overview" \
  -H "X-API-Key: vio_live_your_api_key_here"

Example Response:

json
{
  "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/vouchers

Query Parameters:

ParameterTypeRequiredDescription
fromDatedatetimeNoStart date for analytics
toDatedatetimeNoEnd date for analytics

Example Request:

bash
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:

json
{
  "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/users

Query Parameters:

ParameterTypeRequiredDescription
fromDatedatetimeNoStart date for analytics
toDatedatetimeNoEnd date for analytics

Example Request:

bash
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:

json
{
  "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/tokens

Query Parameters:

ParameterTypeRequiredDescription
fromDatedatetimeNoStart date for analytics
toDatedatetimeNoEnd date for analytics

Example Request:

bash
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:

json
{
  "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

FieldTypeConstraints / FormatDescription
_idstringMongoDB ObjectId (24 hex chars)Unique identifier
namestringMin: 1 char. TrimmedVoucher display name
descriptionstringMay be empty "". No max lengthVoucher description (plain text)
valuenumber>= 0. Default: 0Discount value. Interpretation depends on valueType
valueTypestringEnum: fixed, percentage. Default: fixedHow value is applied: flat amount or percentage rate
valueCurrencystringISO 4217 code (e.g., THB, HKD, USD). Default: THBCurrency for fixed-value vouchers
voucherTypestringEnum: cash, discount, product, cash_discountVoucher business category
consumptionTypestringEnum: vio_code, coupon_code, url, qr_code, manual, zhichongVoucher consumption mode
termsstringMay be empty "". No max lengthTerms and conditions
imagesstring[]Each element: valid URL. May be empty []Image URLs. First image is primary display
isActivebooleanDefault: trueWhether voucher is active and claimable
isTransferablebooleanDefault: falseWhether claimed vouchers can be transferred
totalQuantityinteger-1 = unlimited; >= 0 = limited. Default: -1Total available for claiming
claimedQuantityinteger>= 0. Default: 0Number already claimed
maxClaimsPerUserinteger0 = unlimited; >= 1 = limited. Default: 1Max claims per individual user
startDatestringISO 8601 datetime. Default: creation timeWhen voucher becomes valid
endDatestringISO 8601 datetime. null = no expiryWhen voucher expires
visibilitystringEnum: private, public, shared. Default: privateAccess scope. Also controls transfer scope between users
categorystringEnum: Wellness, Health, Food & Beverage, Leisure & Entertainment, Travel & Hospitality, Lifestyle & Services, Others. NullablePrimary voucher category
categoriesstring[]Array of strings. Each trimmedCategory tags for filtering
minSpendnumber>= 0. Default: 0Minimum purchase amount required. 0 = no minimum
maxDiscountnumberMust be > 0 when set. NullableMax discount cap (useful for percentage vouchers)
settlementAmountnumber>= 0. Default: 0Settlement amount for cross-tenant accounting
settlementCurrencystringISO 4217 code. Default: THBSettlement currency
externalProviderstringProvider code. NullableExternal voucher provider, e.g. vouchain
externalIdstringProvider-side template ID. NullableExternal voucher template ID
externalRequiresDirectOrderParamsbooleanDefault: falseWhether external redemption requires extra account params
applicableScopestringEnum: all_outlets, partial_outlets, single_storeStore applicability scope
bookingEnabledbooleanDefault: falseWhether booking is enabled
bookingDaysInAdvanceinteger>= 0. Default: 0How many days in advance users can book
createdAtstringISO 8601 datetime. Auto-generatedCreation timestamp
updatedAtstringISO 8601 datetime. Auto-updatedLast update timestamp

VoucherClaim (UserVoucher)

FieldTypeConstraints / FormatDescription
_idstringMongoDB ObjectId (24 hex chars)Unique identifier
voucherIdstringMongoDB ObjectId (24 hex chars)Associated voucher template ID
userIdstringMongoDB ObjectId (24 hex chars)Owner user ID
statusstringEnum: active, claimed, redeemed, expired. Default: activeCurrent claim status
redemptionCodestringUnique. Auto-generated format: VCH-{base36_timestamp}-{4_random_chars}Code for redeeming the voucher
claimedAtstringISO 8601 datetime. Default: current timeWhen the voucher was claimed
redeemedAtstringISO 8601 datetime. null until redeemedWhen the voucher was redeemed
expiresAtstringISO 8601 datetime. null = no expiry. Inherited from voucher's endDateWhen this claim expires
redemptionDetails.locationstringOptionalWhere the voucher was redeemed
redemptionDetails.notesstringOptionalRedemption notes
redemptionDetails.methodstringOptionalRedemption method, e.g. api_key or pin
externalVoucherCodestringNullableFulfilled external voucher code
externalRedemptionUrlstringNullableFulfilled external redemption URL
externalOrderIdstringNullableExternal provider order ID
externalFulfillmentStatusstringEnum: pending, processing, fulfilled, failed, nullExternal fulfillment status

User

FieldTypeConstraints / FormatDescription
_idstringMongoDB ObjectId (24 hex chars)Unique identifier
emailstringEmail format. null if not set. At least one of email/phone requiredEmail address
phonestringPhone number format. null if not setPhone number
displayNamestringnull if not set. TrimmedDisplay name
avatarstringValid URL. null if not setAvatar URL
rolestringEnum: member, sub_company_admin, tenant_admin, super_adminUser role
isActivebooleanDefault: trueActive status
walletAddressstringEthereum address format (0x...). Auto-provisioned on creationWeb3 custodial wallet address
registrationSourcestringEnum: created, direct, store, campaignHow the user was registered
storeIdobjectPopulated reference or null. Contains _id (ObjectId), name (string)Store if registered via store
campaignIdobjectPopulated reference or null. Contains _id (ObjectId), name (string), slug (string)Campaign if registered via campaign
metadataobjectKey-value pairs. Values can be any typeCustom metadata
createdAtstringISO 8601 datetime. Auto-generatedRegistration timestamp
updatedAtstringISO 8601 datetime. Auto-updatedLast update timestamp

Campaign

FieldTypeConstraints / FormatDescription
_idstringMongoDB ObjectId (24 hex chars)Unique identifier
namestringMin: 1 char. TrimmedCampaign display name
descriptionstringMay be empty "". No max lengthCampaign description (plain text)
slugstringLowercase, URL-safe. Unique per tenant + sub-company. Auto-generated from nameURL-friendly identifier for campaign pages
tokenIdstringMongoDB ObjectId (24 hex chars). RequiredToken users spend to claim vouchers in this campaign
isActivebooleanDefault: trueWhether campaign is active. Inactive = hidden from users
isPublicbooleanDefault: trueWhether campaign is publicly accessible (no login required)
startDatestringISO 8601 datetime. Default: creation timeWhen the campaign starts
endDatestringISO 8601 datetime. null = no end dateWhen the campaign ends
imagesstring[]Each element: valid URL. May be empty []Campaign banner/cover images
createdAtstringISO 8601 datetime. Auto-generatedCreation timestamp

Token

FieldTypeConstraints / FormatDescription
_idstringMongoDB ObjectId (24 hex chars)Unique identifier
namestringTrimmedToken display name
symbolstringTypically 2-5 uppercase charactersToken symbol (e.g., LP, RC)
descriptionstringMay be empty "". No max lengthToken description
totalSupplystringNumeric string for precisionTotal supply
decimalsinteger>= 0. Default: 0Decimal places
isActivebooleanDefault: trueActive status
createdAtstringISO 8601 datetime. Auto-generatedCreation timestamp
updatedAtstringISO 8601 datetime. Auto-updatedLast update timestamp

TokenBalance

FieldTypeConstraints / FormatDescription
tokenIdstringMongoDB ObjectId (24 hex chars)Token ID
tokenNamestringTrimmedToken display name
symbolstringTypically 2-5 uppercase charactersToken symbol
balancestringNumeric string for precision. >= "0"Available (spendable) balance
lockedBalancestringNumeric string for precision. >= "0"Balance locked in pending operations

Transaction

FieldTypeConstraints / FormatDescription
_idstringMongoDB ObjectId (24 hex chars)Unique identifier
tokenIdstringMongoDB ObjectId (24 hex chars)Token ID
typestringEnum: mint, transfer, burn, reward, redeem, expireTransaction type
amountstringNumeric string for precision. > "0"Transaction amount
fromUserIdstringMongoDB ObjectId (24 hex chars). null for mint operationsSender user ID
toUserIdstringMongoDB ObjectId (24 hex chars). null for burn operationsRecipient user ID
memostringnull if not provided. No max lengthTransaction memo/reason
createdAtstringISO 8601 datetime. Auto-generatedTransaction timestamp

Pagination

FieldTypeConstraints / FormatDescription
pageinteger>= 1Current page number (1-indexed)
limitinteger1 - 100Items per page
totalinteger>= 0Total items matching the query
totalPagesinteger>= 0Total pages available
hasNextPagebooleantrue or falseWhether more pages exist after this
hasPrevPagebooleantrue or falseWhether pages exist before this

12. Code Examples

JavaScript/Node.js Example

javascript
// 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

python
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

bash
#!/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" | jq

13. Changelog

Version 1.1.0 (April 2026)

Registration Source, Campaign Analytics & PIN Permissions

  • Added registrationSource field to User model (created, direct, store, campaign)
  • Added storeId and campaignId populated references in User responses
  • Added campaign visit and registration tracking analytics
  • New public endpoint: POST /api/campaigns/:id/track-visit for campaign page visit tracking
  • Campaign list now includes analytics object with visits and registrations counts
  • Added permissions field 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-pin response now includes a permissions array
  • Voucher consumption by PIN (POST /vouchers/redeem/:code/pin) now requires the PIN to have the voucher_redemption permission
  • Existing PINs without explicit permissions default to voucher_redemption for 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.

VIO v4 Platform Documentation