Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.zupy.com/llms.txt

Use this file to discover all available pages before exploring further.

$ZUPY (abbreviated Z$) is a blockchain-backed loyalty token on the Solana network — its units are called ZUPYies. Customers earn Z$ automatically when they accumulate points, and can use Z$ to “top up” reward redemptions when they don’t have enough points.
Prerequisites: Your API key (zupy_pk_*) and familiarity with the Authentication page. Z$ distribution is automatic — partners do NOT need to call a separate endpoint to award Z$.

What Are Z$ Tokens?

PropertyDetail
Full name$ZUPY (abbreviated Z$; units: ZUPYies)
BlockchainSolana mainnet
Token typeCompressed SPL token (Merkle tree)
Precision6 decimal places
EarningAutomatic — triggered when points are added
UsageTop up reward redemptions when points are insufficient
TransferabilityNot transferable between customers via API
Each customer has a Z$ wallet (compressed token account on Solana). The wallet is created automatically on the customer’s first Z$ distribution — no setup needed from partners.

How Customers Earn Z$

Z$ tokens are distributed automatically when a partner adds points to a customer. There is no separate endpoint to call.
POST /api/v2/customers/{id}/points/add/   →   Points added
                                           →   Z$ bonus distributed (async)
Partners do NOT need to call a separate endpoint to award Z$. When you call POST /api/v2/customers/{id}/points/add/, the Z$ bonus is distributed automatically as a background task.

Key Details

  • Automatic: Z$ distribution is a side effect of points addition
  • Async: Distribution happens via a background worker (Celery task) — not instant
  • Configured by Zupy: The Z$ bonus amount per points addition is configured by Zupy ops per company, not by partners
  • Wallet creation: If the customer doesn’t have a wallet yet, one is created on first distribution
# Adding points also triggers Z$ distribution automatically
curl -X POST "https://api.zupy.com/api/v2/customers/2awTHloSJX7kGGprFerOOsvABcd/points/add/" \
  -H "X-API-Key: zupy_pk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"amount": 50, "reason": "Order #12345"}'

Checking Z$ Balance

Real-Time On-Chain Balance

Query the customer’s real-time Z$ balance directly from the Solana blockchain:
GET /api/v2/customers/{id}/z-balance/
Response fields:
FieldTypeDescription
z_balancestringZ$ balance with 6 decimal places (e.g., "150.000000")
wallet_addressstringSolana wallet address (compressed token account)
has_walletbooleanWhether the customer has a Z$ wallet
errorstring | nullError message if Solana query failed (e.g., "timeout")
curl -X GET "https://api.zupy.com/api/v2/customers/2awTHloSJX7kGGprFerOOsvABcd/z-balance/" \
  -H "X-API-Key: zupy_pk_your_api_key_here"
Example response:
{
  "data": {
    "z_balance": "150.000000",
    "wallet_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
    "has_wallet": true,
    "error": null
  },
  "meta": {}
}
Graceful degradation: If the Solana network is slow or unreachable, the endpoint returns HTTP 200 with "error": "timeout" or "error": "unavailable" and z_balance may be null. Your integration should handle this gracefully — show a “balance unavailable” message rather than failing.

Cached Balance (Customer Profile)

The customer profile also includes a cached Z$ balance:
GET /api/v2/customers/{id}/
The response includes a zupy_balance field:
{
  "data": {
    "id": "2awTHloSJX7kGGprFerOOsvABcd",
    "full_name": "Maria Santos",
    "points_balance": 350,
    "zupy_balance": "150.000000",
    ...
  },
  "meta": {}
}
zupy_balance in the customer profile is a cached off-chain value. It may lag behind the actual on-chain balance by a few minutes. For real-time balance, use the /z-balance/ endpoint.
MethodEndpointFreshnessUse When
Real-timeGET /api/v2/customers/{id}/z-balance/Live on-chain queryDisplaying balance before redemption, showing wallet details
CachedGET /api/v2/customers/{id}/zupy_balanceMay lag a few minutesShowing balance in a list view, non-critical display

Using Z$ in Reward Redemption (Top-Up)

When a customer doesn’t have enough points for a reward, Z$ tokens can cover the gap. This is called a top-up.
POST /api/v2/customers/{id}/rewards/{reward_id}/redeem/
Request body:
{ "use_z_tokens": true }

How Top-Up Works

  1. Customer wants a reward that costs 500 points
  2. Customer only has 450 points
  3. With use_z_tokens: true, Z$ covers the 50-point gap (up to 20% of the reward cost)
  4. A 20% markup is applied to the Z$ portion
Response includes additional Z$ fields:
FieldTypeDescription
z_tokens_usedstringZ$ tokens spent on this redemption
new_z_balancestringCustomer’s remaining Z$ balance after redemption
curl -X POST "https://api.zupy.com/api/v2/customers/2awTHloSJX7kGGprFerOOsvABcd/rewards/2bxRKmpWJY8lHHqsGfsQQtwCDef/redeem/" \
  -H "X-API-Key: zupy_pk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"use_z_tokens": true}'
Example response (top-up scenario):
{
  "data": {
    "coupon_code": "CZ-X9Y8Z7W6",
    "points_used": 450,
    "z_tokens_used": "60.000000",
    "new_balance": 0,
    "new_z_balance": "1090.000000",
    "valid_until": "2026-04-25T00:00:00Z"
  },
  "meta": {}
}
In the example above:
  • Reward cost: 500 points
  • Customer has: 450 points + 1,150 Z$
  • Points used: 450 (all available points)
  • Points gap: 50 points
  • Z$ cap: max 20% of reward cost = 100 points coverable by Z$ (50 is within cap)
  • Z$ equivalent: 50 points = 50 Z$
  • 20% markup: 50 Z$ × 1.20 = 60 Z$
  • Z$ used: 60.000000
  • Remaining Z$: 1,150 - 60 = 1,090.000000
The Z$ top-up can cover at most 20% of the reward’s point cost. If the points gap exceeds this limit, the redemption will fail with a 422 error. The markup percentage is configured globally by Zupy and is not visible to partners via the API.

Z$ Economy on Marketing Coupons (Settlement Splits)

Marketing coupons (rewards with origin_type=marketing) are funded by the company’s Z$ budget and pay out on every redemption. The split is public because every actor in the marketplace — including the integration partner — may receive Z$ from it. Every redemption distributes settlement_budget Z$ split between four actors. Each percentage is a share of the full settlement_budget (not of a sub-portion):
              settlement_budget (per redemption = 100%)

   ┌──────────────────────────┼──────────────────────────┐
   │              │           │           │              │
   │              │           │           │              │
   │ Customer    Operator    Publisher    Sharer         │
   │  50.00%   *_split_pct   *_split_pct *_split_pct      │
   │ (hardcoded)    └─────────┴───────────┘              │
   │              must sum to exactly 50.00              │
   └──────────────────────────────────────────────────────┘
                Customer 50  +  Other 3 splits 50  = 100
  • Customer share is always 50% — exposed on the API as customer_split_pct = "50.00".
  • The 3 configurable percentages must sum to exactly 50.00 so that customer + the three together equal 100% of settlement_budget. Any individual split can be 0 if the merchant chose to allocate everything to the other actors.
  • operator_split_pct may be the integration partner, not the merchant — if your integration is the entity publishing the coupon, this is your commission. Always check this field for your share.

Fields on GET /api/v2/rewards/

For each reward (loyalty or marketing) the catalog returns:
FieldMeaning
settlement_budgetTotal Z$ paid out per redemption (the 100% of the pie)
customer_split_pctAlways "50.00" — the customer’s share
operator_split_pct% of settlement_budget going to the operator (merchant or integration partner)
publisher_split_pct% of settlement_budget going to the marketplace publisher
sharer_split_pct% of settlement_budget going to whoever shared the viral link
z_distributed_totalTotal Z$ already distributed = used_redemptions × settlement_budget
origin_typemarketing (this rule applies) or loyalty_reward (points-based redemption)

Worked example

A marketing coupon with settlement_budget = "100.00", operator_split_pct = "0.00", publisher_split_pct = "24.00", sharer_split_pct = "26.00":
  • Customer: 100 × 0.50 = 50 Z$ (the hardcoded 50%)
  • Operator: 100 × 0.00 = 0 Z$ (merchant gave up its share)
  • Publisher: 100 × 0.24 = 24 Z$
  • Sharer: 100 × 0.26 = 26 Z$
  • Total distributed = 100 Z$ (full settlement_budget ✓)
After 5 used redemptions, z_distributed_total = "500.00" (5 × 100).

Important Notes & FAQ

No. Z$ tokens cannot be transferred between customers via the API. Each customer’s Z$ balance is tied to their individual compressed token account on Solana.
No. The Z$ distribution amount per points addition is configured by Zupy ops, not by partners. Contact Zupy support if you need to discuss bonus amounts for your company.
Yes. Z$ tokens exist on Solana mainnet as compressed SPL tokens stored in a Merkle tree. They are real blockchain assets, not just database entries. The /z-balance/ endpoint queries the Solana blockchain in real time.
If a customer has never received Z$ tokens, has_wallet will be false in the /z-balance/ response. A wallet is automatically created when the customer first receives Z$ (triggered by a points addition). No action is needed from the partner.
The /z-balance/ endpoint has a timeout. If Solana is slow or unreachable, the response returns HTTP 200 with "error": "timeout" and the balance may be unavailable. Use the cached zupy_balance from the customer profile as a fallback.
No. Z$ is designed to be transparent to customers. They see a “Z$ balance” in their loyalty interface — the blockchain implementation is an internal detail. Partners should present Z$ as a supplementary loyalty currency, not a crypto token.

Next Steps

Coupon Lifecycle

Learn the complete coupon flow from reward browsing to validation

OTP Flow

Set up customer identity verification for sensitive operations

API Reference

Browse all endpoints with request/response schemas