Sell game products for Coins
Game products let a creator sell a durable unlock or a consumable quantity for Coins. Portals owns the balance, price confirmation, payment, entitlement, receipt, refund, and abuse controls. Your game works only with released SKUs and the resulting game-specific inventory.
Real Coin purchases are currently closed while Portals validates the platform. You can configure products, publish exact catalogs, and use the isolated sandbox now. The Monetization tab always labels sandbox Coins as fake; no sandbox action touches a real player balance or creator earnings. Cash-out rates, estimates, and quotes remain internal until cash-outs launch.
1. Configure products
Open My Games → your game → Monetization. Only the game owner can change monetization.
For each product, set:
- a permanent lowercase SKU such as
double_jumporhealth_potion; - a player-facing name and description;
- durable for a one-time unlock, or consumable for quantities the game uses;
- a price from 10 through 5,400 Coins;
- the quantity granted by one purchase;
- an optional lifetime purchase limit per player; and
- an optional PNG, JPEG, or WebP icon stored inside the game project.
A retired SKU can never be reused. This keeps old receipts and player inventories unambiguous.
Configure products with an agent
The Portals plugins for Codex and Claude expose the owner-only catalog and sandbox paths directly, so an agent building the game can complete ordinary draft setup without asking you to repeat it in My Games:
list_web_gamesresolves the game ID.get_game_economy_catalogreads every current product and the optimistic-concurrencydraft_revision.- If the SKU or product kind was not specified, the agent proposes them for confirmation because
both become permanent catalog identity choices on the first upsert. It then uses
update_game_economy_catalogwithaction: "upsert"and the current revision. Use the returned revision for the next product. An upsert replaces every field of an existing product, so the agent reads first and resends every value that should remain. - The agent writes game code against those exact SKU strings.
test_game_economy_purchaseexercises success, cancellation, insufficient balance, retryable failure, consume, refund, and revocation behavior in the isolated purchase sandbox. It creates a fresh operation ID per action, so the agent verifies consume idempotency separately in game code or automated tests by repeating the same gameplay event ID.
action: "retire" is permanent and should be used only when withdrawing that SKU is explicitly
intended. Agent-authored changes still affect only the draft. They reach players only after Portals
reviews the exact catalog and the owner explicitly publishes a game release containing it. The
tools cannot approve a catalog, enable rollout, cash out Coins, or spend a real player's balance.
2. Test in the sandbox
The Monetization tab includes a 10,000-fake-Coin sandbox. Test successful purchase, cancellation, insufficient balance, retryable failure, consume, refund, and reset. Durable ownership, quantities, purchase limits, and idempotent retries behave like the production contract, but the data is isolated from real Coins.
The SDK economy calls are unavailable in the ordinary editor iframe. Use the Monetization sandbox for transaction behavior and keep a normal non-purchase fallback in your game while real purchases are closed.
3. Publish the catalog
Publishing freezes the current product catalog into that exact immutable game release. A later product edit does not change a live or scheduled release. Publish again to ship a new catalog.
Before that release can be sold, Portals reviews the exact active product names, descriptions, types, prices, quantities, limits, and icons in the current draft. The Monetization tab shows whether the draft needs review, was approved, or was rejected. Any product change creates a new draft revision and requires a new review; approval never carries over to changed content. Product review is separate from creator identity verification and game moderation.
Before a creator can accept real Coins, Portals independently requires:
- a verified email and account in good standing;
- Stripe Connect identity verification;
- a supported creator history or an operator grant;
- an accessible live game with an exact released catalog; and
- open platform, creator, game, and product controls.
The Monetization tab shows the safe next action for each gate without exposing abuse-detection details.
4. Read the released catalog
Build shop UI from getCatalog(). Do not hardcode a price or accept a price from game state: the returned release catalog and Portals confirmation are authoritative.
const products = await Portals.economy.getCatalog();
for (const product of products) {
console.log(product.sku, product.title, product.coinPrice);
}
Each product contains sku, title, description, kind, coinPrice, grantQuantity, purchaseLimitPerPlayer, and iconPath.
5. Ask Portals to purchase
Call purchase() directly from a fresh player click or tap. The game supplies only a released SKU. Portals signs the player in if needed, creates a short-lived quote, and displays trusted UI outside the game iframe with the game, creator, product, price, current balance, and balance after purchase.
buyButton.addEventListener("click", async () => {
buyButton.disabled = true;
try {
const result = await Portals.economy.purchase("health_potion");
if (result.status === "purchased") {
await refreshInventory();
}
} catch (error) {
console.error(error.code, error.message);
} finally {
buyButton.disabled = false;
}
});
The promise resolves with { status, sku, receiptId, quantity }. Cancellation is a normal { status: "cancelled" } result. A script-triggered request rejects with PLAYER_ACTION_REQUIRED.
At confirmation, Portals atomically rechecks the signed launch, current game version, catalog hash, SKU, price, seller readiness, Coin balance, per-product cap, daily limits, and all controls. A stale quote cannot purchase a changed release.
6. Read and use inventory
Game inventory is separate from Marketplace inventory. It contains only entitlements granted by this game.
async function refreshInventory() {
const inventory = await Portals.economy.getInventory();
const potions = inventory.find((item) => item.sku === "health_potion");
potionCount.textContent = String(potions?.quantity || 0);
}
For a consumable, use a stable operation ID that identifies one gameplay event. Reuse that ID if the request times out and you retry; Portals will not consume twice.
const result = await Portals.economy.consume(
"health_potion",
1,
`heal_${matchId}_${eventSequence}`
);
console.log("remaining", result.quantity);
Never grant an item from a score, client save, animation event, or local success screen. Grant only from the inventory returned after Portals confirms the purchase.
Limits and support
- Product price: 10–5,400 Coins.
- Per buyer and game: 13,500 Coins per UTC day.
- Per buyer across games: 27,000 Coins per UTC day.
- A creator can make a live owner test purchase from their own game. The normal price, balance, daily, and per-product limits still apply; the product is granted, the creator earns 0 Coins, and Portals retains the full Coin price. Owner test purchases do not count as demand, creator earnings, or launch evidence.
- Product, game, creator, buyer, and platform controls can pause purchase, consume, or refund independently.
- Support refunds restore the exact original Coin provenance. If a creator already spent the proceeds, Portals still refunds the player and records the creator recovery exposure.
Creators receive 100% of the Coins from a posted third-party game sale. Owner test purchases are the exception: the creator receives 0 Coins and Portals retains the full price. This is not a promise of a fixed USD value. Cash-out is closed, and no creator-facing conversion rate or cash-out quote is published.
TypeScript declarations are available at portals.d.ts.
