npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@goodz-core/sdk

v0.27.9

Published

Official SDK for the GoodZ Ecosystem — unified API client for Core, Commerce, Exchange, Alive, and Shops. Includes OAuth helpers, Z-coin utilities, and React UI components.

Downloads

464

Readme

@goodz-core/sdk

Official SDK for the GoodZ ecosystem — a unified, type-safe API client for building GoodZ-powered applications. One package covers all five products: Core, Commerce, Exchange, Alive, and Shops. Stripe-style namespaces route requests to the correct service transparently.

Installation

npm install @goodz-core/sdk
# or
pnpm add @goodz-core/sdk

Runtime dependency: superjson only. Works in Node.js 18+, Deno, Bun, Cloudflare Workers, and browsers (any runtime with Fetch API).

Prerequisites: Get Your Credentials

Before calling any authenticated API, you need a GoodZ OAuth App (clientId + clientSecret) and a user access token. There are two ways to get set up — choose based on who is doing the integration:

| Path | Who | How to register | How to get token | |------|-----|----------------|------------------| | AI Agent | An MCP-connected Agent (Claude, GPT, etc.) | Call register_app() on Core MCP → credentials returned in response | Call get_auth_url() → user logs in → token returned | | Human Developer | A person writing code | Go to Developer Console → Register App → copy credentials | Implement OAuth flow with buildAuthorizationUrl() + exchangeCode() |

Agent path (zero-dashboard):

# Connect to GoodZ Core MCP
MCP endpoint: https://goodzcore.manus.space/api/mcp

# Step 1: Register an app (no auth needed to call this)
register_app({ app_name: "My App", redirect_uris: ["https://myapp.com/callback"] })
→ { client_id: "od_xxxxxxxx", client_secret: "gk_live_..." }  # Save these!

# Step 2: Get user token
get_auth_url()
→ { login_url: "https://..." }  # User clicks this, logs in, gets token

Human path (traditional):

  1. Visit https://goodzcore.manus.space/dev-hub and log in
  2. Click "Register App" → fill in name and redirect URIs
  3. Copy clientId and clientSecret (secret shown once!)
  4. Implement the OAuth flow in your app (see Authentication below)

Don't need user auth? For read-only public data (catalog, card details, instance profiles), no credentials are needed — just call createGoodZClient() without a token.

Quick Start

import { createGoodZClient } from "@goodz-core/sdk";

const goodz = createGoodZClient({
  accessToken: "your-jwt-token",
});

// Core — settlement
const balance = await goodz.zcoin.getMyBalance();
const trade = await goodz.zcoin.commercialTransfer({
  instanceId: 90447,
  buyerUserId: 1,
  sellerUserId: 2,
  priceZcoin: 1050,       // 10.50 Z-coin in hundredths
  saleType: "direct",
  referenceId: "order-abc-123",
  appClientId: "od_myapp",
});

// Commerce — register a merchant and launch a campaign
const merchant = await goodz.commerce.getOrCreateMerchant({ name: "Frostpeak Store" });
const campaign = await goodz.commerce.launchCampaign({ merchantId: merchant.id, name: "Spring Drop" });
const order = await goodz.commerce.executePurchase({ campaignId: campaign.id, quantity: 1 });

// Exchange — list an item for sale
const listing = await goodz.exchange.createListing({
  instanceId: 42,
  listingType: "fixed_price",
  askPrice: 2000,
});
const data = await goodz.exchange.getMarketData({ coreGoodzId: 42 });

// Alive — chat with a character
const reply = await goodz.alive.sendMessage({
  instanceId: 1,
  userId: 1,
  message: "What's your favorite memory?",
});

// Shops — create and publish a storefront
const themes = await goodz.shops.listThemes({ mood: "warm" });
const storefront = await goodz.shops.createStorefront({
  slug: "frostpeak-official",
  name: "Frostpeak Official Store",
  merchantId: merchant.id,
  config: { version: 1, meta: {}, theme: themes[0].theme, merchantId: merchant.id, sections: [] },
});
await goodz.shops.publishStorefront({ id: storefront.id });

Architecture

The SDK is built on three principles:

One client, all services. createGoodZClient() returns a single object with namespaces for every GoodZ product. Core uses tRPC HTTP wire protocol; sub-sites (Commerce, Exchange, Alive) use MCP JSON-RPC 2.0. The client handles routing transparently — developers never need to know which protocol a method uses.

Hand-crafted types as API contract. All input/output types are defined inside the SDK itself, mirroring the Zod schemas on Core and the MCP tool schemas on sub-sites. This is the same approach used by the Stripe SDK — types serve as both documentation and compile-time contract. When any service's API evolves, the SDK types are updated and a new version is published.

Zero tRPC dependency. The SDK speaks the tRPC wire protocol directly using fetch + superjson, and speaks MCP JSON-RPC natively. Consumers never need to install @trpc/client, @trpc/server, or any MCP client library.

Z-coin Unit Convention

All monetary values in the GoodZ ecosystem use Z-coin hundredths as the internal unit:

| Internal Value | Display Value | Meaning | |---------------|---------------|----------| | 100 | "1.00" | 1 Z-coin | | 1050 | "10.50" | 10.50 Z-coin | | 50 | "0.50" | 0.50 Z-coin |

Key rules:

  1. All *Cents fields are Z-coin hundredths, NOT fiat cents. The Cents suffix is a legacy naming convention from Commerce. retailPriceCents: 1050 means 10.50 Z-coin, not $10.50 USD.
  2. All *Zcoin fields are also hundredths. priceZcoin: 2000 means 20.00 Z-coin.
  3. Use *_display fields for UI. Balance responses include both balance_hundredths (for math) and balance_display (for rendering).
  4. get_deposit_url is the exception — its amount parameter uses display units (100 = 100 Z-coin, not 1 Z-coin).

Conversion helper:

import { toDisplay, toHundredths, formatZcoin } from "@goodz-core/sdk/zcoin-utils";

toDisplay(1050);        // 10.5
formatZcoin(1050);      // "10.50"
toHundredths(10.50);    // 1050

Tip for Agent developers: When calling MCP tools, always check the tool description for the unit. Most tools use hundredths, but get_deposit_url uses display units.

Client Configuration

interface GoodZClientConfig {
  coreUrl?: string;           // Default: "https://goodzcore.manus.space"
  commerceUrl?: string;       // Default: "https://goodz-commerce.manus.space"
  exchangeUrl?: string;       // Default: "https://goodz-exchange.manus.space"
  aliveUrl?: string;          // Default: "https://goodz-alive.manus.space"
  shopsUrl?: string;          // Default: "https://goodz-shops.manus.space"
  accessToken?: string;       // Static JWT token
  getAccessToken?: () => string | Promise<string>;  // Dynamic token provider
  headers?: Record<string, string>;  // Custom headers for every request
}

The getAccessToken function takes precedence over accessToken when both are provided. Use it for auto-refresh scenarios. Sub-site URLs default to production endpoints and rarely need to be changed.

API Reference — Core Namespaces

Core namespaces communicate via tRPC HTTP wire protocol with goodzcore.manus.space.

goodz.zcoin — Z-coin Payment & Settlement

| Method | Type | Description | |--------|------|-------------| | getMyBalance() | Query | Authenticated user's Z-coin balance | | getMyHistory(input?) | Query | Transaction history with pagination | | getDepositPackages(input?) | Query | Available deposit packages with pricing | | createDepositOrder(input) | Mutation | Create Stripe checkout for Z-coin deposit | | getDepositStatus(input) | Query | Check deposit checkout session status | | getDepositUrl(input?) | Query | Generate a deposit deep link URL for third-party app redirects | | commercialTransfer(input) | Mutation | Primary API for secondary market. Atomic: debit buyer → credit seller → transfer ownership | | mintAndCharge(input) | Mutation | Primary API for primary sales. Atomic: mint instance → charge buyer | | chargeUser(input) | Mutation | Charge Z-coin for in-app purchases | | createDirectPurchaseOrder(input) | Mutation | Fiat-to-Z-coin direct purchase checkout |

commercialTransfer is the most important method in the SDK. It atomically debits the buyer's Z-coin balance, credits the seller (minus platform fee), and transfers card instance ownership — all in a single database transaction. The referenceId parameter provides idempotency.

mintAndCharge is the primary API for primary sales (gacha, direct-from-creator). It mints a new card instance and charges the buyer in one atomic operation. Requires prior mint authorization via inventory.grantMintAuth.

getDepositUrl generates a deep link URL that redirects users to Core's deposit page. After completing the Stripe checkout, the user is redirected back to your app via returnUrl. This is the recommended way for third-party apps to handle "insufficient balance" scenarios:

// When user's balance is too low, redirect them to deposit
const { url } = await goodz.zcoin.getDepositUrl({
  amount: 100,                          // suggested amount
  returnUrl: "https://myapp.com/shop",  // return here after deposit
  appId: "od_myapp",                    // your app's client_id
});
window.open(url, "_blank");

goodz.inventory — Card Instance Management

| Method | Type | Description | |--------|------|-------------| | getMyInventory(input?) | Query | Authenticated user's own inventory. Supports seriesId, franchiseId, rarity, formFactor filters + sortBy/sortOrder sorting + cursor-based pagination. Returns { items, nextCursor, hasMore } | | getUserInventory(input) | Query | User's owned card instances. Same filters, sorting, and cursor pagination as above | | getStats(input?) | Query | Inventory summary statistics (total instances, franchises, series) with franchise→series breakdown. Supports franchiseId, rarity, formFactor filters | | confirmOwnership(input) | Query | Check if user owns a specific card | | mint(input) | Mutation | Mint new instances (requires franchise ownership) | | batchMint(input) | Mutation | Batch mint multiple cards in one call (max 100 total instances). Requires franchise ownership | | mintFor(input) | Mutation | Authorized party mints — claimed (toUserId) or unclaimed (claim tokens). Requires mint authorization | | mintAsGift(input) | Mutation | Mint as gift — creates unclaimed instances with claim URLs for distribution. Requires franchise ownership | | transfer(input) | Mutation | Transfer instance by instanceId | | transferByCard(input) | Mutation | Transfer by cardId (oldest first) | | grantMintAuth(input) | Mutation | Grant mint authorization to app/user | | revokeMintAuth(input) | Mutation | Revoke a mint authorization. Cannot revoke if locked by an APP | | listMintAuths(input) | Query | List mint authorizations filtered by cardId, granteeUserId, or status | | transferHistory(input) | Query | Ownership/transfer history |

For commercial transactions, always use zcoin.commercialTransfer or zcoin.mintAndCharge instead of raw transfer/mint. The raw methods are for administrative operations and gift transfers only.

goodz.collectible — Card & Instance Queries

| Method | Type | Description | |--------|------|-------------| | getInstanceById(input) | Query | Instance by numeric ID | | getPublicInstance(input) | Query | Instance by instance code | | getPublicInstancesBatch(input) | Query | Batch-fetch instances (max 100) | | getCardProfile(input) | Query | Card metadata, rarity, series info | | getShellImageUrl(input) | Query | Shell (packaging) image URL |

goodz.ip — IP Management (Franchise/Series/Card)

| Method | Type | Description | |--------|------|-------------| | listFranchises(input?) | Query | List all published franchises with optional ownerId, limit, offset filters. No auth required | | getFranchise(input) | Query | Franchise by ID or slug | | getSeries(input) | Query | Series by ID or slug | | listSeriesByFranchise(input) | Query | All series in a franchise | | getCard(input) | Query | Card by ID | | listCardsBySeries(input) | Query | All cards in a series | | listMyFranchises() | Query | Franchises owned by authenticated user (requires auth) |

goodz.platform — App/Merchant Public Info

| Method | Type | Description | |--------|------|-------------| | getAppInfo(input) | Query | Get public info about a registered app/merchant by clientId. Returns name, description, icon, website URL. No auth required |

// Get shop branding for your independent site
const appInfo = await goodz.platform.getAppInfo({ clientId: "od_mymerchant" });
console.log(appInfo.name, appInfo.iconUrl);

goodz.user & goodz.auth

| Method | Type | Description | |--------|------|-------------| | user.getPublicProfile(input) | Query | Profile by openId | | user.getPublicProfileById(input) | Query | Profile by userId | | auth.me() | Query | Authenticated user's profile | | auth.getOAuthAppInfo(input) | Query | OAuth app info by clientId |

goodz.settlement — Creator Revenue & Payouts

| Method | Type | Description | |--------|------|-------------| | getMyBalance() | Query | Settlement credit balance — available balance, lifetime totals, claimed Z-coin, fiat payouts | | getMyHistory(input?) | Query | Transaction history — paginated list of credit/claim/payout events with limit, offset | | claimAsZcoin(input) | Mutation | Claim credits as Z-coin — converts settlement credits to spendable Z-coin. Atomic & idempotent |

Settlement credits ≠ Z-coin. When a buyer purchases GoodZ, the seller’s proceeds accumulate as settlement credits (not direct Z-coin). Sellers can claim credits as Z-coin via claimAsZcoin() or (future) request fiat payout.

// Check creator earnings
const balance = await goodz.settlement.getMyBalance();
console.log(`Available: ${balance.balanceDisplay} Z-coin`);
console.log(`Lifetime earned: ${balance.totalCreditedDisplay}`);

// Claim 1000 Z-coin hundredths (10.00 Z-coin display)
const result = await goodz.settlement.claimAsZcoin({ amount: 1000 });
console.log(`New Z-coin balance: ${result.newZcoinBalanceDisplay}`);

// View transaction history
const history = await goodz.settlement.getMyHistory({ limit: 20 });
for (const tx of history.transactions) {
  console.log(`${tx.txType}: ${tx.amountDisplay} at ${new Date(tx.occurredAt).toISOString()}`);
}

goodz.burn (Destructive Operations)

| Method | Type | Description | |--------|------|-------------| | request(input) | Mutation | Request to burn instances. Returns consent URL for user confirmation | | confirm(input) | Mutation | Confirm burn (called from consent page). Irreversible | | cancel(input) | Mutation | Cancel a pending burn request | | getRequest(input) | Query | Get burn request status | | getReceipt(input) | Query | Get burn receipt (after confirmation) |

Security: Burn is a two-step process. request() creates a pending request and returns a consentUrl. The user MUST be redirected to this URL to confirm. confirm() is only callable from the consent page. This prevents accidental or unauthorized destruction of collectibles.

// Step 1: App requests burn
const { consentUrl } = await goodz.burn.request({
  instanceIds: [101, 102],
  reason: "Recycle for points",
  callbackUrl: "https://myapp.com/burn/callback",
});

// Step 2: Redirect user to consent page
window.location.href = consentUrl;

// Step 3: After user confirms, check receipt
const receipt = await goodz.burn.getReceipt({ requestId: "abc123..." });

API Reference — Commerce Namespace

Commerce methods communicate via MCP JSON-RPC with goodz-commerce.manus.space (46 tools). This namespace covers merchant management, product assembly, campaigns, purchases, wholesale, redemption, and settlement.

Merchant & Product Assembly

| Method | Description | |--------|-------------| | getOrCreateMerchant(input?) | Get or create a merchant (idempotent) | | getMerchantDashboard() | Merchant dashboard with stats | | createBatch(input) | Create a product batch (fixed-price bundle) | | createGachaPool(input) | Create a gacha/blind-box pool with probability weights |

Campaign Lifecycle

| Method | Description | |--------|-------------| | launchCampaign(input) | Launch a new sales campaign | | activateCampaign(input) | Activate a paused campaign | | pauseCampaign(input) | Pause an active campaign | | endCampaign(input) | End a campaign permanently | | updateCampaign(input) | Update campaign details | | getCampaignInfo(input) | Get campaign info | | getCampaignItems(input) | Get items in a campaign |

Purchase & Orders

| Method | Description | |--------|-------------| | executePurchase(input) | Execute a purchase (direct or gacha draw) | | checkOrderStatus(input) | Check order/checkout/batch status by orderId, checkoutSessionId, or batchSessionId. Useful for anonymous purchases | | getMyOrders(input?) | User's order history (paginated: page, page_size) | | getOrderDetail(input) | Get order detail by order_id or order_no | | refundOrder(input) | Refund a confirmed/paid order (merchant owner only) | | cancelOrder(input) | Cancel a pending order (merchant owner only) |

Wholesale

| Method | Description | |--------|-------------| | publishToWholesale(input) | Publish batch to wholesale market | | browseWholesale() | Browse wholesale listings | | procureBatch(input) | Procure a wholesale batch for resale |

Inventory & Fulfillment

| Method | Description | |--------|-------------| | manageInventory(input) | View/manage merchant inventory | | registerInstances(input) | Register minted instances to merchant | | mintToInventory(input) | Mint + register in one step | | requestRedemption(input) | Request physical redemption for an instance | | approveRedemption(input) | Approve a redemption request (merchant owner) | | rejectRedemption(input) | Reject a redemption request (merchant owner) | | shipRedemption(input) | Mark redemption as shipped with tracking info | | confirmRedemptionReceived(input) | Confirm redemption received (buyer) | | cancelRedemption(input) | Cancel a pending redemption | | getRedemptionStatus(input) | Get redemption request status | | getMyRedemptions(input?) | List user's redemption requests |

Wallet / Balance

| Method | Description | |--------|-------------| | getMyBalance(input?) | Query user's real Z-coin balance from Core (single source of truth) | | topUpWallet(input) | Top up Z-coin wallet via Stripe |

Settlement & Discovery

| Method | Description | |--------|-------------| | getSettlementReport() | Revenue and settlement report | | searchMarketplace(input) | Search across all shops | | getShopsByBlueprint(input) | Find shops selling a specific card | | getMerchantownerSkill(input?) | Get merchant owner onboarding guide |

Webhooks & App Management

| Method | Description | |--------|-------------| | registerWebhook(input) | Register event webhook | | listWebhooks(input?) | List registered webhooks | | testWebhook(input) | Test a webhook endpoint | | deleteWebhook(input) | Delete a webhook | | registerApp(input) | Register an app on Commerce | | updateApp(input) | Update app details | | listMyApps() | List your registered apps | | getAuthUrl() | Get Commerce OAuth URL | | learnMerchant(input?) | Get Commerce platform documentation (self-describing) |

Commerce Escape Hatch

// For MCP tools not yet in the typed API
const result = await goodz.commerce.rawTool("some_new_tool", { key: "value" });

API Reference — Exchange Namespace

Exchange methods communicate via MCP JSON-RPC with goodz-exchange.manus.space (23 tools). This namespace covers the secondary market: listings, auctions, want-to-buy requests, P2P trades, watchlist, and market data.

Marketplace & Listings

| Method | Description | |--------|-------------| | browseMarketplace(input?) | Browse active listings with filters (type, GoodZ, condition, price, sort) | | getListingDetail(input) | Get listing details | | createListing(input) | Create listing (fixed_price or auction). Core locks the instance | | cancelListing(input) | Cancel listing. Core unlocks the instance | | getMyListings(input?) | Get your own listings (active, sold, cancelled, expired) |

Purchase & Bidding & Orders

| Method | Description | |--------|-------------| | buyListing(input) | Buy a fixed-price listing. Z-coin debit + ownership transfer | | placeBid(input) | Bid on auction. If meets buy-now price → instant purchase | | getBids(input) | All bids for an auction (highest first) | | getMyOrders(input?) | Your order history (as buyer and/or seller, filterable by role) |

Want-to-Buy (WTB)

| Method | Description | |--------|-------------| | createWtb(input) | Post a want-to-buy request | | browseWtb(input?) | Browse active WTB requests (filter by GoodZ, franchise, series, condition, price) | | fulfillWtb(input) | Fulfill a WTB by offering your item | | cancelWtb(input) | Cancel your WTB request |

P2P Trade

| Method | Description | |--------|-------------| | proposeTrade(input) | Propose an item swap (optional Z-coin compensation) | | respondToTrade(input) | Accept/reject trade. If accepted → all items transfer atomically |

Watchlist & Market Data

| Method | Description | |--------|-------------| | getWatchlist(input?) | Your watchlist (paginated) | | addToWatchlist(input) | Add to watchlist | | removeFromWatchlist(input) | Remove from watchlist | | getMarketData(input) | Price history, floor price, volume, trends. Supports period for aggregation | | getCollectionStats(input) | Collection stats: floor price, listings, 24h/7d volume, avg price |

Batch Lookup & User Profile

| Method | Description | |--------|-------------| | getInstancesBatch(input) | Batch lookup card instances by IDs (max 100) with full metadata | | getUserProfile(input) | Get a user's public profile (displayName, avatar, role) | | learnExchange(input?) | Get Exchange platform documentation |

Exchange Escape Hatch

const result = await goodz.exchange.rawTool("some_new_tool", { key: "value" });

API Reference — Alive Namespace

Alive methods communicate via MCP JSON-RPC with goodz-alive.manus.space (9 tools). This namespace powers AI companion interactions: memory, intimacy, context building, and conversation.

| Method | Description | |--------|-------------| | storeMemory(input) | Store a memory for a character-user pair | | recallMemories(input) | Semantic search for relevant memories | | forgetMemory(input) | Delete a specific memory | | getIntimacy(input) | Get intimacy/affection state between character and user | | updateIntimacy(input) | Update intimacy level after interaction | | buildContext(input) | Build full AI context (profile + memories + history + system prompt) | | classifyIntent(input) | Classify user message into intent category | | sendMessage(input) | Send message → get character response (includes memory/intimacy processing) | | learnAlive(input?) | Get Alive platform documentation |

Alive Escape Hatch

const result = await goodz.alive.rawTool("some_new_tool", { key: "value" });

API Reference — Shops Namespace

Shops methods communicate via MCP JSON-RPC with goodz-shops.manus.space. This namespace manages storefront creation, WCAG-validated themes, asset uploads, and settlement credit management.

Key types: ShopsStorefrontTheme (11 typed properties), ShopsSection (discriminated union of 13 section types), ShopsStorefrontMeta (SEO metadata), ShopsStorefrontConfig (full config). See types-shops.ts for complete definitions.

Storefront CRUD

| Method | Description | |--------|-------------| | createStorefront(input) | Create a new fully-managed storefront from a StorefrontConfig JSON. Returns storefront ID and preview URL | | getStorefront(input) | Get the full StorefrontConfig for a storefront by ID or slug | | updateStorefront(input) | Update a storefront's configuration (partial updates supported) | | publishStorefront(input) | Publish a storefront, making the current draft version live | | previewStorefront(input) | Get the preview URL for a storefront (works for draft and published) |

Themes

| Method | Description | |--------|-------------| | listThemes(input?) | List all 12 WCAG AA-validated theme presets across 6 moods | | generateTheme(input) | Generate a custom StorefrontTheme using AI from a text description |

Assets & Documentation

| Method | Description | |--------|-------------| | uploadAsset(input) | Upload an image/asset to the Shops CDN (base64 or URL) | | learnShops(input?) | Get platform documentation about GoodZ.Shops |

Settlement Credits

| Method | Description | |--------|-------------| | getSettlementBalance() | Get current claimable balance, lifetime earnings, and lifetime claimed | | getSettlementHistory(input?) | Get paginated settlement credit transaction history | | claimSettlementAsZcoin(input) | Claim settlement credits as Z-coin (⚠️ financial operation) |

Shop Branding

| Method | Description | |--------|-------------| | getShopBranding(input) | Get branding info (logo, banner, colors) for a single shop | | batchShopBranding(input) | Batch get branding info for multiple shops (max 50) |

All settlement amounts are in Z-coin hundredths (divide by 100 for display, e.g., 100 = 1.00 Z-coin).

Section Types (13 types)

Storefronts are built from modular sections. The ShopsSection union type covers:

hero · story · campaigns · collection · rarity · features · gallery · testimonials · faq · newsletter · cta · footer · custom_html

Each section type has a dedicated interface (e.g., ShopsHeroSection, ShopsCampaignsSection) with typed properties.


Authentication

Where do I get clientId? See Prerequisites above. AI Agents call register_app() via MCP; human developers register at the Developer Console.

Static Token (simplest)

const goodz = createGoodZClient({
  accessToken: process.env.CORE_ACCESS_TOKEN,
});

Auto-Refresh with TokenManager (recommended)

For long-running services that need automatic token refresh. Pass tokenManager directly — the SDK handles everything:

  • Pre-request: calls tokenManager.getValidToken() to get a fresh token (proactive refresh before expiry)
  • On 401: force-refreshes the token and retries the request once (handles clock skew / server-side revocation)
  • Concurrent dedup: multiple simultaneous 401s trigger only one refresh
  • Callback: onTokenRefresh fires after every successful refresh so you can persist new tokens
import { createGoodZClient } from "@goodz-core/sdk/core";
import { TokenManager } from "@goodz-core/sdk/auth";

const tokenManager = new TokenManager({
  clientId: "od_myapp",
  initialTokens: {
    accessToken: savedAccessToken,
    refreshToken: savedRefreshToken,
    expiresAt: savedExpiresAt,
  },
  onTokenRefresh: async (tokens) => {
    // Persist rotated tokens to your database
    await db.update(appTokens).set({
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
      expiresAt: tokens.expiresAt,
    });
  },
});

// v0.4.0+: pass tokenManager directly — auto-refresh on 401 is built in
const goodz = createGoodZClient({ tokenManager });

// All calls are now fully transparent — no manual token handling needed
const balance = await goodz.zcoin.getMyBalance();       // Core (tRPC)
const dashboard = await goodz.commerce.getMerchantDashboard(); // Commerce (MCP)
const market = await goodz.exchange.browseMarketplace();  // Exchange (MCP)
const memory = await goodz.alive.recallMemories(...);     // Alive (MCP)

Migration from v0.3.x: Replace { getAccessToken: () => tokenManager.getValidToken() } with { tokenManager }. The old pattern still works but does not get 401 auto-retry.

v0.4.1: Auto-refresh now covers all transports — Core (tRPC), Commerce, Exchange, and Alive (MCP). The shared dedup ensures concurrent 401s across different transports trigger only one refresh.

Dynamic Token Provider (legacy)

If you manage tokens yourself without TokenManager:

const goodz = createGoodZClient({
  getAccessToken: async () => {
    const token = await myCustomRefreshLogic();
    return token;
  },
});

Note: This pattern does NOT get automatic 401 retry. Use tokenManager for full auto-refresh.

OAuth Authorization Code Flow with PKCE S256

GoodZ.Core requires PKCE S256 (RFC 7636) for all authorization requests. The SDK provides helpers to generate the code verifier and compute the S256 challenge automatically.

import {
  buildAuthorizationUrl,
  exchangeCode,
  generateCodeVerifier,
} from "@goodz-core/sdk/auth";

// Step 1: Generate PKCE code verifier (store it for the token exchange)
const codeVerifier = generateCodeVerifier();

// Step 2: Build authorization URL (async — computes S256 challenge)
const authUrl = await buildAuthorizationUrl({
  clientId: "od_myapp",
  redirectUri: "https://myapp.com/callback",
  scope: "read write",
  state: crypto.randomUUID(),
  codeVerifier, // SDK auto-computes BASE64URL(SHA256(verifier))
});

// Step 3: Redirect user to authUrl
// ... user authenticates and is redirected back with ?code=xxx&state=xxx

// Step 4: Exchange code for tokens (pass the same verifier)
const tokens = await exchangeCode({
  clientId: "od_myapp",
  clientSecret: process.env.CLIENT_SECRET, // optional for confidential clients
  code: searchParams.get("code"),
  redirectUri: "https://myapp.com/callback",
  codeVerifier, // MUST match the verifier used in Step 2
});

// Step 5: Create a user-scoped client
const userGoodz = createGoodZClient({
  accessToken: tokens.accessToken,
});

PKCE S256 is mandatory. GoodZ.Core rejects code_challenge_method=plain. The buildAuthorizationUrl() function is async because it uses Web Crypto's SHA-256 digest. It works in Node.js 18+, Deno, Bun, Cloudflare Workers, and all modern browsers.

Token Revocation

Revoke a refresh token when the user logs out (RFC 7009):

import { revokeToken } from "@goodz-core/sdk/auth";

// Standalone function
await revokeToken(storedRefreshToken);

// Or via TokenManager
await tokenManager.revokeToken();

Browser-Side Auth (SPA / Vanilla JS)

New in v0.13.0@goodz-core/sdk/browser-auth

For single-page apps or vanilla JS projects that need browser-side OAuth without React:

import { GoodZBrowserAuth } from "@goodz-core/sdk/browser-auth";

// Step 1: Trigger login (redirects to GoodZ login page)
await GoodZBrowserAuth.startLoginAndRedirect({
  clientId: "od_myapp",
  coreBaseUrl: "https://goodzcore.manus.space",
});

// Step 2: Handle callback (call once at app startup)
const handled = await GoodZBrowserAuth.handleCallback({
  clientId: "od_myapp",
  onSuccess: (tokens) => {
    // tokens.access_token, tokens.refresh_token, tokens.open_id
    localStorage.setItem("goodz_token", tokens.access_token);
  },
  onError: (err) => console.error("Login failed:", err.message),
});

How it works:

  • startLoginAndRedirect() generates a PKCE S256 challenge, stores the verifier in sessionStorage, and redirects to Core's authorization endpoint.
  • handleCallback() detects the auth_code URL parameter, retrieves the stored verifier, exchanges the code for tokens, and cleans the URL.
  • Uses Web Crypto API — works in all modern browsers, no Node.js dependencies.

Also available: getAppToken() for M2M client_credentials grants:

import { getAppToken } from "@goodz-core/sdk/browser-auth";

const { access_token } = await getAppToken({
  clientId: "od_xxx",
  clientSecret: "od_sec_xxx",
  scopes: ["ip:read", "inventory:read"],
});

React Auth Provider

New in v0.13.0@goodz-core/sdk/react

For React apps, the Provider + Hook pattern gives you zero-config auth with automatic callback detection, session persistence, and auth state:

// main.tsx — wrap your app
import { GoodZAuthProvider } from "@goodz-core/sdk/react";

ReactDOM.createRoot(root).render(
  <GoodZAuthProvider clientId="od_myapp">
    <App />
  </GoodZAuthProvider>
);
// Any component
import { useGoodZAuth } from "@goodz-core/sdk/react";

function Profile() {
  const { user, isAuthenticated, isLoading, login, logout } = useGoodZAuth();

  if (isLoading) return <Spinner />;
  if (!isAuthenticated) return <button onClick={() => login()}>Sign In</button>;

  return (
    <div>
      <img src={user.picture} alt={user.name} />
      <p>Hello, {user.name}!</p>
      <button onClick={logout}>Sign Out</button>
    </div>
  );
}

Provider props:

| Prop | Type | Required | Description | |------|------|----------|-------------| | clientId | string | Yes | Your OAuth client_id | | coreBaseUrl | string | No | Core URL (default: https://goodzcore.manus.space) | | redirectUri | string | No | Override redirect URI (default: window.location.origin) | | scopes | string | No | OAuth scopes (default: "openid profile email") | | exchangeCode | function | No | Custom code exchange (route through your backend) | | onLogin | function | No | Called after successful login | | onLogout | function | No | Called when logout is triggered | | getUserInfo | function | No | Custom user info fetcher |

Hook return value:

| Field | Type | Description | |-------|------|-------------| | user | UserInfo \| null | Current user (null if not authenticated) | | token | string \| null | Raw access token | | tokenResponse | TokenResponse \| null | Full token response | | isLoading | boolean | Auth state still being determined | | isAuthenticated | boolean | Whether user is authenticated | | error | Error \| null | Last auth error | | login(options?) | function | Initiate login flow | | logout() | function | Clear auth state |

Advanced: Route code exchange through your backend:

<GoodZAuthProvider
  clientId="od_myapp"
  exchangeCode={async (code, codeVerifier, redirectUri) => {
    // Exchange via your backend (e.g., tRPC mutation)
    // Backend can set HttpOnly cookie session
    return await trpc.auth.exchangeGoodZCode.mutate({ code, codeVerifier, redirectUri });
  }}
  onLogin={async (tokens, user) => {
    // Sync user to your database
    await trpc.users.syncFromGoodZ.mutate({ openId: user.openId, name: user.name });
  }}
>
  <App />
</GoodZAuthProvider>

Account Switching (v0.6.1+)

To force re-authentication (e.g., switch from Account A to Account B), pass forceLogin: true:

const switchUrl = buildAuthorizationUrl({
  clientId: "od_myapp",
  redirectUri: "https://myapp.com/callback",
  forceLogin: true, // Appends prompt=login → clears Core session → shows login page
});

This follows the OIDC prompt=login standard. The existing session is cleared server-side before the login page is shown, and the resulting authorization code is valid for the newly authenticated account.

Z-coin Precision

GoodZ stores Z-coin amounts in hundredths (1/100th of a Z-coin). For example, 10.50 Z-coin is stored as 1050.

import { toHundredths, toDisplay, formatZcoin } from "@goodz-core/sdk/zcoin";

toHundredths(10.50);    // → 1050
toDisplay(1050);        // → 10.5
formatZcoin(1050);      // → "10.50"

Always use toHundredths() when constructing API inputs:

// ✅ Correct
await goodz.zcoin.commercialTransfer({
  priceZcoin: toHundredths(10.50),  // 1050
  // ...
});

// ❌ Wrong — this charges 0.10 Z-coin instead of 10.00
await goodz.zcoin.commercialTransfer({
  priceZcoin: 10,
  // ...
});

Error Handling

All API errors are thrown as GoodZApiError instances:

import { GoodZApiError } from "@goodz-core/sdk";

try {
  await goodz.zcoin.commercialTransfer({ /* ... */ });
} catch (err) {
  if (err instanceof GoodZApiError) {
    console.error(err.code);       // "BAD_REQUEST", "FORBIDDEN", etc.
    console.error(err.httpStatus);  // 400, 403, etc.
    console.error(err.path);       // "zcoin.commercialTransfer"
    console.error(err.message);    // Human-readable message
    if (err.zodErrors) {
      for (const fieldErr of err.zodErrors) {
        console.error(`${fieldErr.path.join(".")}: ${fieldErr.message}`);
      }
    }
  }
}

| Code | HTTP | Meaning | Action | |------|------|---------|--------| | BAD_REQUEST | 400 | Invalid input | Check input values | | INSUFFICIENT_BALANCE | 400 | Not enough Z-coin | Redirect to e.depositUrl | | UNAUTHORIZED | 401 | Missing or invalid token | Refresh token or re-login | | FORBIDDEN | 403 | No permission | Show permission denied | | NOT_FOUND | 404 | Resource doesn't exist | Show 404 or fallback | | CONFLICT | 409 | Version conflict | Retry the operation |

Handling Insufficient Balance (Auto-Redirect to Deposit)

When a payment fails due to insufficient Z-coin, the error includes a deposit URL for seamless top-up:

import { GoodZApiError } from "@goodz-core/sdk";

try {
  await goodz.zcoin.mintAndCharge({ /* gacha pull */ });
} catch (err) {
  if (err instanceof GoodZApiError && err.isInsufficientBalance()) {
    // Option 1: Redirect to Core's deposit page (simplest)
    window.open(err.depositUrl, '_blank');

    // Option 2: Build a custom deposit URL with return_url
    const depositUrl = await goodz.zcoin.getDepositUrl({
      amount: 100,  // pre-select 100 Z-coin package
      returnUrl: window.location.href,
      appId: 'od_myapp',
    });
    window.open(depositUrl.url, '_blank');
  }
}

The isInsufficientBalance() helper and depositUrl getter work for all payment methods: commercialTransfer, mintAndCharge, and chargeUser.

UI Components (React)

The SDK includes a React component for displaying GoodZ collectibles with full material effects and tilt interaction — the same experience used on GoodZ.Core itself.

GoodZCardFocus

import { GoodZCardFocus } from "@goodz-core/sdk/ui";

<GoodZCardFocus
  imageUrl="https://goodzcore.manus.space/api/shell/42.png"
  cardName="Luna"
  rarity="SSR"
  formFactor="trading_card"
>
  <img src={thumbnailUrl} alt="Luna" className="w-full rounded-lg" />
</GoodZCardFocus>

Supports all 10 form factors with unique material effects:

| Form Factor | Material Effect | |---|---| | trading_card | Holographic gradient + sparkle (intensity by rarity) | | postcard / postcard_portrait | Paper texture + subtle shadow | | polaroid | White border (thick bottom) + paper texture | | laser_ticket | Intense rainbow holographic + shimmer animation | | badge_* | Circular clip + metal frame + dome highlight | | acrylic_stand_* | Transparent glass + edge refraction + caustic |

Interaction: desktop mouse tilt, mobile gyroscope (auto-detected), touch drag fallback, iOS permission prompt. React 18+ peer dependency. No CSS framework required.

Form Factor Utilities

import { FORM_FACTORS, getAspectRatioCSS, getFormFactorSpec } from "@goodz-core/sdk/ui";

getAspectRatioCSS("trading_card");  // "5 / 7"
getFormFactorSpec("polaroid");      // { key, label, aspectRatio: [3, 4], isCircle: false }

Subpath Imports

For tree-shaking, import from specific subpaths:

import { createGoodZClient } from "@goodz-core/sdk/core";
import { TokenManager } from "@goodz-core/sdk/auth";
import { toHundredths } from "@goodz-core/sdk/zcoin";
import { GoodZCardFocus } from "@goodz-core/sdk/ui";
import { createCommerceNamespace } from "@goodz-core/sdk/commerce";
import { createExchangeNamespace } from "@goodz-core/sdk/exchange";
import { createAliveNamespace } from "@goodz-core/sdk/alive";
import { createShopsNamespace } from "@goodz-core/sdk/shops";

The root import (@goodz-core/sdk) re-exports everything for convenience.

In-App Gacha Quick Start

This is the fastest path to adding GoodZ gacha (blind-box) or direct purchases to your app. The SDK handles all MCP wire format, auth token routing, SSE streaming, and error mapping — you never need to construct raw JSON-RPC requests.

Don't do this: If you're manually constructing MCP JSON-RPC requests to Commerce, stop. Use the SDK instead — it handles auth headers, SSE parsing, isError responses, and 401 auto-refresh automatically.

Prerequisites

npm install @goodz-core/sdk

You need:

  • An OAuth client_id (register your app on GoodZ.Core)
  • A Commerce campaign ID (created by the merchant via assembleBatch + launchSalesCampaign + activateCampaign)

Complete Working Example (React)

// App.tsx — Wrap your app with GoodZAuthProvider
import { GoodZAuthProvider } from "@goodz-core/sdk/react";

export default function App() {
  return (
    <GoodZAuthProvider
      clientId="od_your_app_id"
      scopes="openid profile email"  // ⚠️ "openid" is REQUIRED for Commerce
    >
      <GachaPage />
    </GoodZAuthProvider>
  );
}
// GachaPage.tsx — Complete in-app gacha in ~30 lines
import { useGoodZAuth } from "@goodz-core/sdk/react";
import { createGoodZClient, GoodZApiError } from "@goodz-core/sdk";
import { useState } from "react";

const CAMPAIGN_ID = 42; // Your campaign ID

export function GachaPage() {
  const { token, isAuthenticated, login } = useGoodZAuth();
  const [result, setResult] = useState<any>(null);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  if (!isAuthenticated) {
    return <button onClick={() => login()}>Sign in to draw</button>;
  }

  const handleDraw = async () => {
    setLoading(true);
    setError(null);
    try {
      // SDK automatically sends user's JWT to Commerce as Bearer token
      // No need to construct MCP JSON-RPC, handle SSE, or parse isError
      const goodz = createGoodZClient({ accessToken: token! });
      const draw = await goodz.commerce.executePurchase({
        campaignId: CAMPAIGN_ID,
        quantity: 1,
      });
      setResult(draw);
      // draw.items[0].cardName, draw.items[0].imageUrl, draw.items[0].rarity
    } catch (err) {
      if (err instanceof GoodZApiError) {
        if (err.isInsufficientBalance()) {
          setError("Insufficient Z-coin balance. Please top up.");
          // Optionally redirect to deposit:
          // window.open(err.depositUrl!, "_blank");
        } else {
          setError(err.message); // Human-readable error from Commerce
        }
      } else {
        setError("Unexpected error");
      }
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <button onClick={handleDraw} disabled={loading}>
        {loading ? "Drawing..." : "Draw 1x"}
      </button>
      {error && <p style={{ color: "red" }}>{error}</p>}
      {result && (
        <div>
          <h3>You got: {result.items[0].cardName}</h3>
          <img src={result.items[0].imageUrl} alt={result.items[0].cardName} />
          <p>Rarity: {result.items[0].rarity}</p>
        </div>
      )}
    </div>
  );
}

Server-Side Example (Node.js / Express)

import { createGoodZClient } from "@goodz-core/sdk";
import { TokenManager } from "@goodz-core/sdk/auth";

// For server-side: use TokenManager for automatic token refresh
const tokenManager = new TokenManager({
  clientId: "od_your_app_id",
  clientSecret: process.env.GOODZ_CLIENT_SECRET!,
  initialTokens: {
    accessToken: userAccessToken,   // From OAuth callback
    refreshToken: userRefreshToken, // From OAuth callback
    expiresAt: tokenExpiresAt,
  },
  onTokenRefresh: async (tokens) => {
    // Persist refreshed tokens to your database
    await db.updateUserTokens(userId, tokens);
  },
});

const goodz = createGoodZClient({ tokenManager });

// Execute purchase — SDK handles everything:
// ✅ Sends user JWT (not app M2M token) to Commerce
// ✅ Parses MCP JSON-RPC + SSE response format
// ✅ Checks result.isError and throws GoodZApiError
// ✅ Auto-refreshes token on 401 and retries
const draw = await goodz.commerce.executePurchase({
  campaignId: 42,
  quantity: 1,
});

console.log(draw.orderId);          // number
console.log(draw.items[0].cardName); // string
console.log(draw.items[0].imageUrl); // string
console.log(draw.totalCharged);      // number (Z-coin hundredths)

What the SDK Handles For You

| Concern | Without SDK (manual) | With SDK | |---------|---------------------|----------| | MCP wire format | Construct JSON-RPC 2.0 envelope, set jsonrpc, method, params.name, params.arguments | Just call goodz.commerce.executePurchase({...}) | | Auth header | Manually set Authorization: Bearer <token> | Automatic from accessToken or tokenManager | | SSE streaming | Parse text/event-stream, extract last data: line | Handled internally | | isError response | Check result.isError in MCP content | Throws GoodZApiError automatically | | Token refresh on 401 | Detect 401, refresh token, rebuild headers, retry | Automatic with tokenManager | | Insufficient balance | Parse error message for balance hint | err.isInsufficientBalance() + err.depositUrl | | Commerce URL | Hardcode https://goodz-commerce.manus.space/api/mcp | Built-in default | | Session management | Track mcp-session-id header | Automatic |

Common Mistakes

| Mistake | Symptom | Fix | |---------|---------|-----| | Using APP M2M token for purchase | "This campaign does not allow anonymous purchases" | Use user's access token, not client_credentials token | | Missing openid in OAuth scope | Commerce can't identify the user | Add "openid" to scope: scopes="openid profile email" | | Passing Core userId to Commerce | "User not found" or wrong user | Don't pass userId — SDK extracts it from the JWT automatically | | Manually constructing MCP requests | Parsing errors, missed isError, no auto-refresh | Use the SDK |


Purchase Flow Guide

This section walks through the end-to-end purchase flow for a typical GoodZ-powered app.

Flow Overview

User visits merchant storefront → Browses campaigns → Selects item → Executes purchase
    → Z-coin deducted → GoodZ minted to user → Order confirmed

Step 1: Display Merchant & Campaigns

// Get merchant info (no auth required)
const appInfo = await goodz.platform.getAppInfo({ clientId: "od_merchantapp" });

// Browse active campaigns
const campaigns = await goodz.commerce.searchMarketplace({
  merchant_id: merchantId,
});

Step 2: Execute Purchase

// Direct purchase (fixed-price batch)
const order = await goodz.commerce.executePurchase({
  campaign_id: campaignId,
  quantity: 1,
  purchase_type: "direct",
});

// Gacha draw (blind-box)
const draw = await goodz.commerce.executePurchase({
  campaign_id: gachaCampaignId,
  quantity: 3,
  purchase_type: "gacha",
});

Step 3: Handle Insufficient Balance

import { GoodZApiError } from "@goodz-core/sdk";

try {
  await goodz.commerce.executePurchase({ /* ... */ });
} catch (err) {
  if (err instanceof GoodZApiError && err.isInsufficientBalance()) {
    // Redirect to deposit page
    const { url } = await goodz.zcoin.getDepositUrl({
      returnUrl: window.location.href,
      appId: "od_myapp",
    });
    window.open(url, "_blank");
  }
}

Step 4: Check Order Status

// Poll order status (useful for async/anonymous purchases)
const status = await goodz.commerce.checkOrderStatus({
  orderId: order.orderId,
});
// status.status: "pending" | "confirmed" | "failed" | "refunded"

Step 5: View Inventory

// First page of user's inventory
const page1 = await goodz.inventory.getMyInventory({ limit: 50 });
console.log(page1.items); // InventoryItem[]

// Next page via cursor (more efficient than offset for large collections)
if (page1.nextCursor) {
  const page2 = await goodz.inventory.getMyInventory({
    limit: 50,
    cursor: page1.nextCursor,
  });
}

// Filter by rarity + form factor
const ssrCards = await goodz.inventory.getMyInventory({
  rarity: "SSR",
  formFactor: "trading_card",
});

// Sort by card name A→Z
const alphabetical = await goodz.inventory.getMyInventory({
  sortBy: "cardName",
  sortOrder: "asc",
});

// Sort by franchise name, then paginate with offset
const byFranchise = await goodz.inventory.getMyInventory({
  sortBy: "franchiseName",
  sortOrder: "asc",
  limit: 20,
  offset: 0,  // Use offset pagination for non-acquiredAt sorts
});
// Next page: offset: 20, then offset: 40, etc.
// Check byFranchise.hasMore to know if more pages exist

// Sort by instance number (ascending)
const byNumber = await goodz.inventory.getMyInventory({
  sortBy: "instanceNumber",
  sortOrder: "asc",
  franchiseId: 42,  // Combine with filters
});

// Inventory stats summary
const stats = await goodz.inventory.getStats();
console.log(`You own ${stats.totalInstances} cards across ${stats.totalFranchises} franchises`);

// Stats filtered by rarity
const ssrStats = await goodz.inventory.getStats({ rarity: "SSR" });

Step 6: Secondary Market (Optional)

// List an item for sale on Exchange
await goodz.exchange.createListing({
  instance_id: 12345,
  listing_type: "fixed_price",
  price_zcoin: toHundredths(50), // 50 Z-coin
});

// Browse marketplace
const listings = await goodz.exchange.browseMarketplace({
  sort: "price_asc",
});

Entry Points

The SDK is organized into focused entry points. Import only what you need:

| Entry Point | Import Path | Description | |-------------|-------------|-------------| | Main | @goodz-core/sdk | Types, createGoodZClient, SDK_VERSION | | Core | @goodz-core/sdk/core | createGoodZClient (detailed) | | Auth | @goodz-core/sdk/auth | Server-side: TokenManager, exchangeCode, revokeToken, buildAuthorizationUrl | | Browser Auth | @goodz-core/sdk/browser-auth | Browser-side: GoodZBrowserAuth, getAppToken, clearAppTokenCache | | React | @goodz-core/sdk/react | GoodZAuthProvider, useGoodZAuth (wraps browser-auth) | | Z-coin | @goodz-core/sdk/zcoin | toHundredths, toDisplay, formatZcoin | | UI | @goodz-core/sdk/ui | GoodZCardFocus React component | | Commerce | @goodz-core/sdk/commerce | createCommerceClient | | Exchange | @goodz-core/sdk/exchange | createExchangeClient | | Alive | @goodz-core/sdk/alive | createAliveClient | | Shops | @goodz-core/sdk/shops | createShopsNamespace |

Tree-shaking: Each entry point is a separate ESM chunk. Importing @goodz-core/sdk/zcoin does not pull in React or the full API client.

Migration from Standalone Files

If you were previously copying goodz-auth.ts and goodz-auth-react.tsx into your project, you can now use the npm package instead:

| Before (manual copy) | After (npm) | |---------------------|-------------| | import { GoodZBrowserAuth } from "./goodz-auth" | import { GoodZBrowserAuth } from "@goodz-core/sdk/browser-auth" | | import { GoodZAuthProvider, useGoodZAuth } from "./goodz-auth-react" | import { GoodZAuthProvider, useGoodZAuth } from "@goodz-core/sdk/react" | | import { getAppToken } from "./goodz-auth" | import { getAppToken } from "@goodz-core/sdk/browser-auth" |

The API is identical — just change the import path. The standalone files remain available for zero-dependency scenarios.

Migration from Raw MCP Calls

If you were previously calling Commerce/Exchange/Alive MCP endpoints directly via fetch() or curl, the SDK eliminates all that boilerplate:

Before (manual MCP JSON-RPC)

// ❌ Manual: 30+ lines of boilerplate per call
const res = await fetch("https://goodz-commerce.manus.space/api/mcp", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${userToken}`,  // Must be USER token, not APP token!
    "Accept": "application/json, text/event-stream",
  },
  body: JSON.stringify({
    jsonrpc: "2.0",
    method: "tools/call",
    params: { name: "execute_purchase", arguments: { campaignId: 42, quantity: 1 } },
    id: 1,
  }),
});

// Must handle SSE vs JSON response format
const contentType = res.headers.get("content-type") || "";
let body;
if (contentType.includes("text/event-stream")) {
  const text = await res.text();
  const lines = text.split("\n");
  const lastData = lines.filter(l => l.startsWith("data:")).pop()?.slice(5).trim();
  body = JSON.parse(lastData!);
} else {
  body = await res.json();
}

// Must check isError in MCP content
if (body.result?.isError) {
  throw new Error(body.result.content[0].text);  // Easy to miss!
}

// Must extract JSON from MCP text content
const result = JSON.parse(body.result.content[0].text);

After (SDK)

// ✅ SDK: 3 lines, fully typed, auto-refresh, error handling built-in
import { createGoodZClient, GoodZApiError } from "@goodz-core/sdk";

const goodz = createGoodZClient({ accessToken: userToken });
const result = await goodz.commerce.executePurchase({ campaignId: 42, quantity: 1 });
// result is typed as CommerceDrawResult — { orderId, items, totalCharged, platformFee }

What the SDK handles that you no longer need to:

  1. MCP JSON-RPC envelopejsonrpc, method, params.name, params.arguments, id
  2. SSE response parsingtext/event-stream content type detection and extraction
  3. isError checking — Automatically throws GoodZApiError when Commerce returns an error
  4. Session managementmcp-session-id header tracking across calls
  5. 401 auto-refresh — When using TokenManager, expired tokens are refreshed and retried
  6. Commerce URL — Default https://goodz-commerce.manus.space built-in
  7. Type safety — Full TypeScript types for all inputs and outputs

Changelog

0.13.0 (2026-03-28)

Browser Auth + React Provider in npm

  • @goodz-core/sdk/browser-auth: New entry point — GoodZBrowserAuth (PKCE S256 + sessionStorage), getAppToken() (M2M with caching), clearAppTokenCache(). Zero Node.js dependencies, uses Web Crypto API.
  • @goodz-core/sdk/react: New entry point — GoodZAuthProvider + useGoodZAuth() hook. Automatic callback detection, session persistence, auth state management. Supports custom exchangeCode for backend-routed token exchange.
  • Migration path: Developers who previously copied goodz-auth.ts / goodz-auth-react.tsx can now npm install and change import paths. API is identical.
  • Entry Points table: Added comprehensive entry points documentation.
  • React remains an optional peer dependency — browser-auth works without React.

0.12.0 (2026-03-28)

Inventory Sorting

  • sortBy parameter: Inventory queries (getMyInventory, getUserInventory) now accept sortBy with values: "acquiredAt" (default), "cardName", "seriesName", "franchiseName", "instanceNumber". Combine with sortOrder: "asc" | "desc" for full control.
  • hasMore field: Response now includes hasMore: boolean for reliable pagination detection, especially useful with offset-based pagination when using non-acquiredAt sorts.
  • franchiseName in items: Each inventory item now includes franchiseName for display without additional lookups.
  • Cursor + sort compatibility: Cursor-based pagination remains available for acquiredAt sort (most efficient). For other sort fields, use offset-based pagination (offset parameter).
  • MCP sync: get_user_inventory MCP tool also supports sort_by and sort_order parameters.
  • New types: InventorySortBy, SortOrder exported from SDK.

0.17.1 (2026-04-02)

Commerce V4.0 Merchant Migration — Patch

  • JSDoc cleanup: Replaced all remaining "shop" / "shop owner" references in Commerce SDK JSDoc comments with "merchant" / "merchant owner" across types-commerce.ts, commerce/index.ts, and README.md.
  • Dashboard type fix: CommerceMerchantDashboard.stats.shopSharemerchantShare to match Commerce V4.0 actual response field totalMerchantShareCents.
  • README examples: Updated all code examples from createShop / shopId to getOrCreateMerchant / merchantId. Updated method reference table.
  • dist rebuild: Regenerated dist/index.d.ts to eliminate stale createShop JSDoc in published types.

0.17.0 (2026-04-02)

Commerce V4.0 Merchant Migration

  • BREAKING: createShopgetOrCreateMerchant (idempotent). All shopId params → merchantId. getShopDashboardgetMerchantDashboard. getShopsByBlueprintgetMerchantsByBlueprint. learnShoplearnMerchant. getShopownerSkillgetMerchantownerSkill.
  • New campaign params: anonymousAllowed, maxPerDevice, maxPerUser, periodType, periodQuota, periodCustomDays added to CommerceLaunchCampaignInput.
  • Type renames: All CommerceShop* types → CommerceMerchant*. CommerceCreateShopInputCommerceGetOrCreateMerchantInput (name now optional).

0.16.1 (2026-04-01)

MCP Alignment — Full Sub-site SDK Sync

  • Commerce (46 tools): Added 12 missing methods: requestRedemption, approveRedemption, rejectRedemption, shipRedemption, confirmRedemptionReceived, cancelRedemption, getRedemptionStatus, getMyRedemptions, learnMerchant, getMerchantownerSkill, topUpWallet, listWebhooks. Removed orphan redeemPhysical (replaced by requestRedemption). Fixed 6 parameter mismatches (updateCampaign +period, executePurchase +fingerprint/urls, etc.).
  • Shops (14 tools): Added 2 new methods: getShopBranding, batchShopBranding.
  • Exchange (23 tools): Added 2 missing methods: getWatchlist (now paginated), learnExchange. Fixed buyListing (+referenceId), getMarketData (+period).
  • Alive (9 tools): Added learnAlive method.
  • Type exports: All new input/output types exported from respective namespace modules.

0.11.0 (2026-03-28)

Sprint 5 — Creator/Developer APIs + Settlement Namespace

  • inventory.batchMint(): Batch mint multiple cards in one call. Each item specifies cardId and quantity (max 100 total instances). Requires franchise ownership. Returns per-card results.
  • inventory.mintFor(): Authorized party mints instances. Two modes: claimed (toUserId provided → instant ownership) or unclaimed (toUserId omitted → returns claim tokens/URLs for anonymous distribution). Requires active mint authorization.
  • inventory.mintAsGift(): Mint unclaimed instances with claim tokens for gift distribution. Requires franchise ownership. Returns claim URLs with optional expiry.
  • inventory.revokeMintAuth(): Revoke a mint authorization. Cannot revoke if locked by an APP.
  • inventory.listMintAuths(): List mint authorizations filtered by cardId, granteeUserId, or status.
  • settlement namespace: New namespace for creator revenue management:
    • getMyBalance() — settlement credit balance with lifetime totals
    • getMyHistory() — paginated transaction history
    • claimAsZcoin() — convert settlement credits to spendable Z-coin
  • Type exports: SettlementNamespace, BurnNamespace, MintAuthorization, and all new input/output types.

0.10.0 (2026-03-28)

Sprint 4 — Rarity/FormFactor Filtering + Cursor Pagination

  • rarity filter: All inventory endpoints (getMyInventory, getUserInventory, getStats) now accept a rarity parameter for exact-match filtering (e.g. "SSR", "Rare"). Creator-defined labels.
  • formFactor filter: All inventory endpoints accept a formFactor parameter ("trading_card", "badge_small", etc.) for filtering by physical form factor.
  • Cursor-based pagination: getMyInventory() and getUserInventory() now return { items, nextCursor } instead of a flat array. Pass nextCursor as cursor in the next request for efficient keyset pagination. Offset-based pagination still supported for backward compatibility.
  • FormFactorKey type: Exported from SDK for type-safe form factor filtering.
  • InventoryPage type: New response type with items: InventoryItem[] and nextCursor?: number.
  • Breaking change: getMyInventory() and getUserInventory() return shape changed from InventoryItem[] to InventoryPage. Migrate: const items = (await goodz.inventory.getMyInventory()).items;

0.9.0 (2026-03-28)

Sprint 3 — PKCE S256 + Inventory Stats + Token Revocation + Purchase Guide

  • PKCE S256: buildAuthorizationUrl() is now async and computes S256 code challenges automatically. Added generateCodeVerifier() and computeCodeChallenge() helpers. GoodZ.Core requires S256 — the old plain method was never accepted by the server.
  • inventory.getStats(): New endpoint returning inventory summary statistics (total instances, franchises, series) with breakdown by franchise → series. Supports franchiseId filter.
  • revokeToken(): Standalone function and TokenManager.revokeToken() method for RFC 7009 token revocation on logout.
  • Purchase Flow Guide: End-to-end documentation covering merchant display → purchase → balance handling → order status → inventory → secondary market.
  • Deprecation: buildAuthorizationUrlSync() is deprecated (uses plain PKCE which Core rejects). Will be removed in v1.0.

0.8.0 (2026-03-28)

Sprint 2 — Inventory Filtering + Platform Info

  • franchiseId filter: Added franchiseId parameter to inventory.getMyInventory() and inventory.getUserInventory(). Users can now filter their inventory by franchise (IP), the most requested feature for independent sites.
  • platform.getAppInfo(): New platform namespace with getAppInfo({ clientId }) — returns app/shop public info (name, description, icon, website URL). No auth required. Enables independent sites to display shop branding.
  • Backend: Added franchiseId filter to tRPC inventory.mine and inventory.getUserInventory, and MCP get_user_inventory. Added platform.getAppInfo tRPC endpoint and get_app_info MCP tool.

0.7.0 (2026-03-28)

Sprint 1 — Convenience Methods

  • inventory.getMyInventory(): Convenience wrapper for authenticated user's own inventory (maps to inventory.mine). Supports seriesId, franchiseId, limit, offset.
  • commerce.checkOrderStatus(): Check order/checkout/batch status by orderId, checkoutSessionId, or batchSessionId. Essential for anonymous purchase flows.
  • ip.listFranchises(): List all published franchises with optional ownerId, limit, offset filters. No auth required.
  • Confirmed commerce.listWebhooks() already exists.

0.6.1 (2026-03-28)

  • Account Switching: Added forceLogin?: boolean to OAuthUrlConfig in buildAuthorizationUrl(). When true, appends prompt=login to the authorize URL, forcing re-authentication (OIDC standard). Requires GoodZ.Core v2.18+.

0.6.0

  • Initial stable release with Commerce, Exchange, Alive namespaces

License

MIT