Overview
A credit-based subscription gives your customers a wallet of credits that they spend to unlock individual pieces of content — an article, an e-paper edition, a webinar. The customer buys a credit-based offer once, receives a balance of credits, and then pays for each item with credits instead of money.
This guide covers the part of the integration that is not handled automatically for you: what your website must do when a customer clicks a paywalled article and wants to redeem a credit to read it. Configuring the offer and the wallet in the Merchant Backend is covered in the articles under Before you begin; this guide starts where those leave off.
Note
Redeeming a credit is a server-to-server operation. Frisbii Media does not ship a ready-made "spend credits" button in the Checkout or JS-SDK paywall. Your backend calls the wallet API, and your application decides what the customer may read. This gives you full control over the paywall experience, but the access logic described below is your responsibility to implement.
How the pieces fit together
Concept | What it is | Where it is created |
|---|---|---|
Credit-based offer | The offer the customer buys (single product or subscription). Subscription types: | Merchant Backend → Offers |
Wallet | The credit account, identified by a | Merchant Backend → Products, linked to the offer |
Credit | A unit in the wallet. One purchase adds the offer's consumption units to the wallet. | Filled automatically on purchase/recharge |
Redemption | Spending credits from the wallet — the | Your backend (this guide) |
Access | The customer being allowed to read the item after redemption. | Your application (this guide) |
The end-to-end flow for a single article looks like this:
sequenceDiagram
participant C as Customer (browser)
participant P as Your paywall (JS-SDK / SSO)
participant S as Your backend
participant FM as Frisbii Media API
Note over C,FM: Setup — done earlier
C->>FM: Buy credit-based offer (Checkout)
FM-->>FM: Fill wallet (availableCreditCount up)
Note over C,FM: Runtime — redeeming a credit
C->>P: Click paywalled article
P->>FM: SSO login
FM-->>P: Authenticated session
P->>S: Request article (with SSO session)
S->>FM: GET /wallets/{customerId}/{uniqueId}
FM-->>S: availableCreditCount
alt Already unlocked
S-->>C: Serve article (no credit spent)
else Enough credits
S-->>C: Show "Read for 1 credit"
C->>S: Confirm
S->>FM: POST /wallets/use
FM-->>S: 200 OK (balance down, usage logged)
S->>S: Record unlock (or create access right)
S-->>C: Serve article
else Not enough credits
S-->>C: Offer top-up / purchase
endThe setup steps (buying the offer, filling the wallet) are covered by existing documentation. The runtime steps — SSO, balance check, redemption, and serving the article — are what this guide documents.
Before you begin
You have created a wallet and a credit-based offer, and you know the wallet's
uniqueId. See Credit-Based Offers, Credit-based subscription and Wallets.Customers can already buy the offer through your Checkout integration. A completed purchase fills the wallet automatically.
You have a server-side API access token with the Wallets access group enabled. See API Access Tokens. The token is sent in the
X-plenigo-tokenheader and must never be exposed in the browser.Your paywall identifies the logged-in customer through SSO , so your backend can resolve the customer's Frisbii Media
customerId.
Step 1: Identify the customer through SSO
Credit redemption is tied to a specific customer, so your backend needs the customer's Frisbii Media customerId before it can touch the wallet.
Log the customer in — Use the JS-SDK SSO or Cookie-based SSO on your paywall so the visitor has an authenticated Frisbii Media session.
Resolve the
customerId— From the SSO session (or, if you run External User Management, from your own mapping of your user to the Frisbii Media customer), obtain thecustomerIdon your server.
Do the wallet calls from your server, not the browser
The wallet endpoints are authenticated with your merchant API token. Never call them from client-side JavaScript, and never ship the token to the browser. The browser's job is only to authenticate the customer (SSO) and to trigger a request to your backend.
Step 2: Read the wallet balance
Before you show the "Read for 1 credit" prompt, check how many credits the customer has. Query the customer's wallet by its uniqueId.
Request
GET /api/v3.0/wallets/{customerId}/{uniqueId}
Host: api.frisbii-media.com
X-plenigo-token: {your_api_access_token}curl -X GET \
"https://api.frisbii-media.com/api/v3.0/wallets/CUSTOMER_ID/my-article-wallet" \
-H "X-plenigo-token: {your_api_access_token}"Response (200 OK)
{
"customerCreditWalletId": 480123,
"customerId": "CUSTOMER_ID",
"uniqueId": "my-article-wallet",
"title": "Article credits",
"availableCreditCount": 8,
"creditValidityTimeStart": "2026-07-01T09:00:00Z"
}Use availableCreditCount to drive your paywall:
availableCreditCount >= creditsNeeded— show the "Read for 1 credit" prompt.availableCreditCount < creditsNeeded— show a top-up / buy path (start a new Checkout for the credit-based offer) instead.
To display history or reconcile, you can also use GET /api/v3.0/wallets/ (all wallets for the customer), GET /api/v3.0/wallets/creditsUsed, and GET /api/v3.0/wallets/creditsUploaded.
Step 3: Redeem the credit
When the customer confirms, your backend spends credits from the wallet with a single call. This is the core of the integration.
Request
POST /api/v3.0/wallets/use
Host: api.frisbii-media.com
X-plenigo-token: {your_api_access_token}
Content-Type: application/json{
"customerId": "CUSTOMER_ID",
"uniqueId": "my-article-wallet",
"creditsUsed": 1,
"reason": "article:2026-07-29-market-outlook",
"category": "article"
}Field | Required | Notes |
|---|---|---|
| Yes | The Frisbii Media customer ID. |
| Yes | The wallet's unique ID (the credit account to debit) — not an article ID. |
| Yes | Number of credits to spend. Min |
| Yes | Free text, max 100 characters. This is the only place the redeemed item is recorded — put your own article reference here (see the caveat below). |
| No | Free text, max 100 characters. Useful for grouping/reporting (e.g. |
On success (200 OK) Frisbii Media atomically:
decrements
availableCreditCountbycreditsUsed,writes a credit usage record (visible via
GET /wallets/creditsUsedand in the Wallet Change History), andif the balance reaches 0 on a subscription wallet, schedules the subscription's next billing so the wallet recharges according to its configured recharge type.
On insufficient balance (400 Bad Request) the response carries the message "Wallet does not contain enough credits." (Frisbii Media error entity 102000, detail code 1). Handle it as "cannot unlock" and route the customer to a top-up / purchase.
Step 4: Grant access and serve the article
This is the step that Frisbii Media leaves to you, and the reason the redemption flow can feel "missing".
Important: redeeming a credit does not create access to a specific article
The
/wallets/usecall debits the wallet and logs a usage record. It does not store which article was unlocked (only your free-textreason/category), and it does not create an access right for that article.Every call to
/wallets/usespends credits again. If the customer reloads the page or clicks twice, you will charge a second credit unless you prevent it.
Your application must therefore (a) remember what the customer has unlocked and (b) enforce that on every subsequent visit.
You have two ways to implement access. Choose one and apply it consistently.
Option A — Keep the unlock record in your own system (simplest)
Before redeeming, check your own store for an existing
(customerId, articleId)unlock. If it exists, serve the article and skip the/wallets/usecall — the customer already paid.After a successful redemption, persist the
(customerId, articleId)unlock (and, ideally, the returned usage record) in your database.On every future request for that article, serve it based on your stored unlock, not by calling
/wallets/useagain.
This keeps all article-level logic on your side and is the most direct fit for the wallet API.
Option B — Convert the redemption into a Frisbii Media access right (recommended if you already use access rights)
If you already gate content with Access Rights and Check Access, turn each successful redemption into a durable access right so the same hasAccess check protects both money-based and credit-based content.
After
/wallets/usereturns200, create an access right for the article, using a stableaccessRightUniqueIdper item (for example, the same identifier you use elsewhere for that article):POST /api/v3.0/accessRights/{customerId}/ X-plenigo-token: {your_api_access_token}On every future visit, validate access with the standard check — no wallet call needed:
GET /api/v3.0/accessRights/{customerId}/hasAccess?accessRightUniqueIds=article:2026-07-29-market-outlook{ "accessGranted": true }
Tip: make redemption idempotent
With either option, check "does the customer already have access to this item?" before calling /wallets/use. That single guard prevents double-charging on reloads, retries and double-clicks — the most common integration bug with credits.
Endpoint reference
Base URL (production): https://api.frisbii-media.com/api/v3.0. Confirm the host for your environment in API versions. Authentication: X-plenigo-token header, merchant API access token with the Wallets access group (and Access Rights for Option B).
Operation | Method & path | Purpose |
|---|---|---|
Read a wallet |
| Get |
List wallets |
| All wallets (paginated). |
Redeem credits |
| Spend credits from a wallet. |
Credit usage history |
| Audit spent credits. |
Credit upload history |
| Audit added credits. |
Add credits manually |
| Top up a wallet outside a purchase. |
Refill wallets |
| Trigger the configured recharge. |
Create access right |
| (Option B) Grant durable article access. |
Check access |
| (Option B) Validate access on later visits. |
Troubleshooting
Symptom | Likely cause |
|---|---|
|
|
| Wrong |
Customer charged twice for one article | No idempotency guard — you called |
| Token missing the Wallets access group, wrong environment token, or the token was sent from the browser. |