API Documentation

Learn how to integrate Volia vouchers into your application.

Getting Started

Volia provides a RESTful API for voucher generation, validation, and redemption. All API endpoints are available at the base URL of your Volia instance.

Base URL

// All API routes are prefixed with
https://your-domain.com/api

Authentication Methods

Volia supports two authentication methods:

  • Session Cookie — Used by the web application after login. Automatically handled by the browser.
  • API Key — For server-to-server integration. Include via Authorization: Bearer <key> header.

API Keys

Generate API keys from the API Keys page in your dashboard. Keys are prefixed with vh_live_ and are shown only once upon creation.

Using API Keys

// Pass your API key as a Bearer token
curl -X GET https://your-domain.com/api/vouchers \
  -H "Authorization: Bearer vh_live_<your-key>"
// Or with JavaScript fetch
await fetch('https://your-domain.com/api/vouchers', {
  headers: {
    Authorization: `Bearer ${apiKey}`
  }
})

⚠️ Keep your API keys secure

Never expose API keys in client-side code. Always use them from a backend service.

Voucher Types

Vouchers are split into two categories:

Voucher · Fixed

Fixed dollar amount discount. Category: discount

{ category: "discount", type: "fixed", value: 20 }

Voucher amount of $20.00 per use. Fund is reserved.

Voucher · Percentage

Percentage discount with a dollar cap.

{ category: "discount", type: "percentage", value: 10, maxRedeemValue: 50 }

10% off, capped at $50.00 per use.

Benefit

Non-monetary benefit (e.g. free shipping, early access).

{ category: "benefit", description: "Free shipping" }

No fund reservation. type and value are ignored.

Note: Percentage vouchers require maxRedeemValue. Benefit vouchers require description instead.

Voucher Status

Each voucher follows a status lifecycle:

StatusDescriptionRedeemable
activeVoucher is valid and ready to be redeemed.✅ Yes
usedAll max uses have been exhausted.❌ No
expiredVoucher has passed its expiration date.❌ No
revokedAdministratively voided or campaign deleted.❌ No

Automatic transitions:

  • activeused — When redeemCount >= maxUses
  • activeexpired — When expiresAt < now (checked on redeem/validate)
  • activerevoked — Campaign or group is deleted

Code Format

Voucher codes are generated from a format pattern. Each X in the pattern is replaced with a random alphanumeric character (A-Z, 0-9).

Pattern Rules

  • Maximum 50 characters
  • Allowed characters: A-Z, 0-9, X, -
  • If count > 1, pattern must contain X
  • No consecutive, leading, or trailing dashes

Examples

Pattern

SUMMER-XXXX

Possible codes

SUMMER-A3K9, SUMMER-7XQ2, SUMMER-P5MN

Pattern

XXXXXX

Possible codes

B7K9M2, XR4PQ8, 3VN2WJ

Pattern

SALE-XX-2024

Possible codes

SALE-A3-2024, SALE-9K-2024

Pattern

VIP

Possible codes

VIP (single code, no X needed)

The prefix is automatically derived from the pattern — everything before the first X, with trailing dashes removed. If no prefix is found, defaults to GEN.

Generate Vouchers

Create one or more vouchers for a customer. Requires authentication (session or API key).

POST/api/vouchers

Request Body

{
  "format": "SUMMER-XXXX",
  "type": "fixed",
  "value": 20,
  "campaignId": "uuid-here",
  "count": 5,
  "maxUses": 1,
  "expiresAt": "2026-12-31",
  "maxRedeemValue": 50,
  "group": "Batch-1",
  "minPurchase": 100
}

Parameters

NameTypeRequiredDescription
formatstringYesCode pattern (e.g. "SUMMER-XXXX")
typestringYes"fixed" or "percentage"
valuenumberYesVoucher amount in dollars or percent
campaignIdstringYesCampaign to reserve fund from
countnumberNoQuantity (1-100, default: 1)
maxUsesnumberNoMax redemptions per voucher (default: 1)
expiresAtdateNoExpiration date (ISO 8601)
maxRedeemValuenumber*Required for percentage type
groupstringNoGroup name within campaign
minPurchasenumberNoMinimum cart total to redeem

Example — cURL

curl -X POST https://your-domain.com/api/vouchers \
  -H "Authorization: Bearer vh_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"format":"SUMMER-XXXX","type":"fixed","value":20,"campaignId":"uuid"}'

Example — JavaScript

const response = await fetch('https://your-domain.com/api/vouchers', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    format: 'SUMMER-XXXX',
    type: 'fixed',
    value: 20,
    campaignId: 'uuid-here',
  })
})
const vouchers = await response.json()

Response

[
  {
    "id": "uuid",
    "code": "SUMMER-A3K9",
    "prefix": "SUMMER",
    "type": "fixed",
    "value": 20,
    "status": "active",
    "redeemCount": 0,
    "maxUses": 1,
    "maxRedeemValue": null,
    "expiresAt": "2026-12-31T00:00:00.000Z",
    "group": "Batch-1",
    "customerId": "user-uuid",
    "createdAt": "2026-07-06T12:00:00.000Z"
  }
]

Validate a Voucher

Check whether a voucher code is valid without redeeming it. Requires authentication — call this endpoint from your backend with a valid API key.

POST/api/validate

Request Body

{
  "code": "SUMMER-A3K9",
  "cartTotal": 150  // optional — used to compute percentage discount
}

Success Response (200)

{
  "valid": true,
  "voucher": {
    "code": "SUMMER-A3K9",
    "category": "discount",
    "description": null,
    "type": "fixed",
    "value": 20,
    "maxRedeemValue": 50,
    "minPurchase": 100,
    "computedDiscount": 20
  }
}

Error Response (200)

{
  "valid": false,
  "error": "Voucher has expired"
}

📌 Discount Calculation

computedDiscount depends on voucher category and type:

  • Fixed: computedDiscount = value — cartTotal is ignored
  • Percentage: computedDiscount = min(cartTotal × value%, maxRedeemValue) — requires cartTotal; returns warning if omitted
  • Benefit: computedDiscount = null — non-monetary

Example — cURL

curl -X POST https://your-domain.com/api/validate \
  -H "Authorization: Bearer vh_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"code":"SUMMER-A3K9"}'

Redeem a Voucher

There are two ways to redeem a voucher:

1. Redeem by Code

Requires authentication — call this endpoint from your backend with a valid API key.

POST/api/redeem

Request Body

{
  "code": "SUMMER-A3K9",
  "customerEmail": "user@example.com",
  "cartTotal": 150
}

Parameters

NameTypeRequiredDescription
codestringYesVoucher code (case-insensitive)
customerEmailstringNoFor tracking purposes
cartTotalnumberNoCart total for percentage discount calculation (ignored for fixed/benefit)

Validation Order

  1. Voucher code exists
  2. Voucher is not revoked (deleted)
  3. Voucher status is active
  4. Voucher has not expired
  5. Usage count is below maxUses
  6. Cart total meets minPurchase (if set)

Example — cURL

curl -X POST https://your-domain.com/api/redeem \
  -H "Authorization: Bearer vh_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"code":"SUMMER-A3K9"}'

Success Response

{
  "success": true,
  "voucher": {
    "code": "SUMMER-A3K9",
    "category": "discount",
    "description": null,
    "type": "fixed",
    "value": 20,
    "maxRedeemValue": 50,
    "computedDiscount": 20
  }
}

Response — Percentage without cartTotal

{
  "success": true,
  "voucher": {
    "code": "SUMMER-PCT",
    "category": "discount",
    "type": "percentage",
    "value": 15,
    "maxRedeemValue": 50,
    "computedDiscount": null,
    "warning": "cartTotal is required to compute percentage discount"
  }
}

Error Response

{
  "statusCode": 400,
  "message": "This voucher has expired"
}

2. Authenticated Redeem (by ID)

Requires authentication. Redeem a voucher by its ID. Only the voucher owner can redeem via this method.

POST/api/vouchers/{id}/redeem

Body (optional): { "trackingEmail": "...", "trackingIdentifier": "..." }

curl -X POST https://your-domain.com/api/vouchers/{voucherId}/redeem \
  -H "Authorization: Bearer vh_live_<your-key>"

Campaigns

Campaigns organize vouchers and manage budgets. Each campaign has a fund (total budget) and usedFund (budget consumed by created vouchers).

Fund Mechanics

When you create vouchers in a campaign, the fund is reserved at creation time:

perVoucher = (type === 'fixed') ? value : maxRedeemValue
totalReserve = perVoucher × maxUses × count
remaining = campaign.fund - campaign.usedFund

// If totalReserve > remaining → Error: Insufficient campaign fund
// Otherwise → campaign.usedFund += totalReserve

Fund is not released on redemption. To free up budget, delete a voucher group (which recalculates usedFund based on remaining active vouchers).

Campaign API

GET/api/campaigns— List your campaigns
POST/api/campaigns— Create a campaign
POST/api/campaigns/{id}— Campaign detail with vouchers

Campaign Statuses

Campaign status is computed dynamically:

  • scheduledstartsAt > now
  • active — Within the date range or no dates set
  • endedendsAt < now

Voucher Groups

Within a campaign, vouchers can be organized into groups — logical labels set at creation time.

  • Group is an optional free-text field
  • Vouchers without a group belong to "Ungrouped"
  • Deleting a group deletes all vouchers in it and recalculates the campaign fund

Error Handling

API errors follow a consistent format:

{
  "statusCode": 400,
  "message": "Description of what went wrong"
}

Common HTTP Status Codes

CodeMeaningTypical Cause
200SuccessRequest completed successfully
400Bad RequestInvalid body, missing fields, or voucher errors (expired, used, revoked)
401UnauthorizedMissing or invalid API key / session
403ForbiddenInsufficient permissions (not admin)
404Not FoundResource not found (voucher, campaign, etc.)
409ConflictDuplicate voucher code, insufficient campaign fund
500Server ErrorUnexpected error — contact support

Common Voucher Errors

Error MessageCause
Voucher not foundCode does not exist
Voucher has been revokedVoucher was deleted or revoked
Voucher has expiredexpiresAt date has passed
Voucher is usedAll max uses exhausted
Insufficient campaign fundCampaign budget is fully reserved
Format pattern is requiredMissing format in generate request
maxRedeemValue is required for percentage typeMissing required field
Need help? Contact support or visit the settings page to manage your account.