Skip to content

Daniel-Ric/PlayFab-Catalog-Service-Bedrock

PlayFab-Catalog Service - Minecraft Bedrock Edition

Node.js/Express API for browsing, comparing, and monitoring PlayFab Catalog marketplace items — with JWT auth, LRU caching, OpenAPI validation, SSE event streams, and webhooks. Built for high throughput with keep-alive HTTP agents, debounced caching, and resilient upstream retries.

Runtime Framework OpenAPI Auth Status

Important

Project Disclaimer
"PlayFab Catalog Service Bedrock" by SpindexGFX is an independent project. It is not affiliated with, endorsed by, sponsored by, or otherwise connected to Mojang AB, Microsoft Corporation, or any of their subsidiaries or affiliates. No partnership, approval, or official relationship with Mojang AB or Microsoft is implied.
All names, logos, brands, trademarks, service marks, and registered trademarks are the property of their respective owners and are used strictly for identification and reference purposes only. This project does not claim ownership of third-party intellectual property and does not grant any license to use it.


Table of Contents


Overview

PlayFab-Catalog Bedrock API provides a read-optimized façade over the PlayFab Catalog. It consolidates listing, search, tag exploration, “featured servers”, price/sale aggregation, as well as advanced search with facets. For live insights, it emits SSE signals for item changes, price changes, featured content updates, and trending creators, and forwards events to external systems using webhooks with signatures and retries.

The service is intentionally stateless (except JSON files on disk) and production-friendly: aggressive keep-alive, retry budget, de-duped in-flight requests, and LRU caches for hot data.


Key Features

  • 🔐 JWT authentication with role guards (admin endpoints).
  • 🎛️ Configurable via environment with sensible defaults.
  • Throughput: Node keep-alive agents + safe retry/jitter/backoff.
  • 🧾 ETags and Cache-Control for CDN/proxy friendliness.
  • OpenAPI schema with optional request/response validation.
  • 🔎 Advanced search (filters, sort, pagination, facets).
  • 🧩 Item enrichment (resolved references, prices, reviews).
  • 🧭 Featured servers backed by config + live catalog resolution.
  • 🛍️ Sales aggregator across stores; per-creator slicing.
  • 📡 SSE for item.*, price.changed, sale.*, creator.trending, featured.content.added, featured.content.removed, featured.content.updated.
  • 🔔 Webhooks with HMAC signature (sha256=<hex> over JSON body) and retry/backoff.
  • 🧰 Tooling: OpenAPI ref fixer, request logger, pagination helpers.

Quickstart

# 1) Clone & install
git clone https://github.com/Daniel-Ric/PlayFab-Catalog-Service-Bedrock
cd playfab-catalog-api
npm ci

# 2) Configure environment
cp .env.example .env
# IMPORTANT: set JWT_SECRET (>= 32 chars), ADMIN_USER/ADMIN_PASS, DEFAULT_ALIAS/TITLE_ID, etc.

# 3) Start (development)
NODE_ENV=development node src/index.js

# 4) Production
NODE_ENV=production LOG_LEVEL=info node src/index.js

Base URL (default): http://localhost:3000

Warning

This service can generate sustained upstream traffic through watchers, enrichment, retries, and webhook delivery. Before using it against a public or production-like environment, review polling intervals, concurrency limits, cache settings, and timeout values to avoid unnecessary load on upstream services.


Configuration (Environment)

Required: JWT_SECRET (>= 32 chars). Server exits if missing/too short.

General

Variable Default Description
PORT 3000 HTTP port
NODE_ENV production development
JWT_SECRET JWT signing secret (>= 32 chars)
ADMIN_USER Username for /login
ADMIN_PASS Password for /login
LOG_LEVEL info error
CORS_ORIGINS http://localhost:5173,https://view-marketplace.net Comma-separated list of allowed browser origins
TRUST_PROXY 1 Express trust proxy
ENABLE_DOCS false Serve Swagger UI at /docs
VALIDATE_REQUESTS false Enable OpenAPI request validation
VALIDATE_RESPONSES false Enable OpenAPI response validation
UPSTREAM_TIMEOUT_MS 20000 Axios timeout for PlayFab requests
HTTP_MAX_SOCKETS 512 Keep-alive sockets (HTTP)
HTTPS_MAX_SOCKETS 512 Keep-alive sockets (HTTPS)

GitHub Version Updates

Variable Default Description
UPDATE_CHECK_ENABLED true Enable GitHub-backed version checks for GET /version
UPDATE_CHECK_REPOSITORY Git remote/package repository GitHub repository in owner/name or GitHub URL format
UPDATE_CHECK_TTL_MS 900000 Cache TTL for GitHub version metadata
UPDATE_CHECK_TIMEOUT_MS 5000 GitHub API request timeout
UPDATE_CHECK_STARTUP_LOG true Log the public GitHub update status after server startup

When UPDATE_CHECK_STARTUP_LOG=true, the listener is opened first and the banner is printed with a version block above the attribution line. The checker uses the public GitHub API without a token. Local SemVer Git tags at the current commit are preferred over package.json; if no local tag is available, a matching local commit and latest GitHub release/tag commit still counts as current.

VERSION CHECK Update available
   Version  Current 1.0.0 (package.json) -> Latest 1.1.0 (GitHub release, ok, 900s cache)
    Commit  Local b2b951b3bdbe on main -> Remote 2f1c4d8a91bd latest ref
      Repo  Daniel-Ric/PlayFab-Catalog-Service-Bedrock
   Release  https://github.com/Daniel-Ric/PlayFab-Catalog-Service-Bedrock/releases/tag/v1.1.0

Optional Catalog Bridge

The Catalog Bridge is disabled by default and adds no routes, handshakes, CORS rules, CSRF checks, or rate limiters unless CATALOG_BRIDGE_ENABLED=true.

Variable Default Description
CATALOG_BRIDGE_ENABLED false Enable optional /api/... bridge routes
CATALOG_BRIDGE_PROXY_ENABLED follows bridge Enable POST /api/catalog/proxy
CATALOG_BRIDGE_SECURE_ENABLED follows bridge Enable handshake and POST /api/catalog/secure
CATALOG_BRIDGE_CSRF_ENABLED follows bridge Require CSRF for plain proxy and secure payloads
CATALOG_BRIDGE_CORS_ENABLED false Add bridge-specific CORS headers
CATALOG_BRIDGE_RATE_LIMIT_ENABLED follows bridge Add bridge-specific rate limiting
ALLOWED_WEB_ORIGINS empty Comma-separated origins for bridge CORS
CATALOG_UPSTREAM_ORIGIN empty Upstream origin used for relative /catalog/... proxy targets
CATALOG_BEARER_TOKEN empty Server-side bearer token injected into upstream catalog requests
CSRF_COOKIE_NAME catalog_bridge_csrf CSRF cookie name
SESSION_COOKIE_NAME catalog_bridge_session Reserved bridge session cookie name if deployments need one
CATALOG_BRIDGE_COOKIE_SAMESITE Lax CSRF cookie SameSite value
CATALOG_BRIDGE_COOKIE_SECURE true in production Add Secure to bridge cookies
CATALOG_BRIDGE_COOKIE_TTL_MS 1800000 CSRF cookie TTL
CATALOG_BRIDGE_HANDSHAKE_TTL_MS 120000 ECDH handshake TTL
CATALOG_BRIDGE_RATE_LIMIT_WINDOW_MS 60000 Bridge rate-limit window
CATALOG_BRIDGE_RATE_LIMIT_MAX 120 Bridge rate-limit max requests per window
CATALOG_BRIDGE_CORS_METHODS GET,POST,OPTIONS Bridge CORS methods
CATALOG_BRIDGE_CORS_ALLOWED_HEADERS Content-Type,X-CSRF-Token Bridge CORS request headers
CATALOG_BRIDGE_CORS_CREDENTIALS true Add bridge CORS credentials support
CATALOG_BRIDGE_UPSTREAM_TIMEOUT_MS 60000 Bridge upstream timeout; independent from the PlayFab timeout
CATALOG_BRIDGE_MAX_BODY_BYTES 1048576 Bridge JSON body limit

POST /api/catalog/proxy accepts only relative url values under /catalog/, blocks /catalog/login, ignores client-supplied Authorization, and injects the configured server-side bearer token. GET /api/security/catalog-handshake creates short-lived P-256 ECDH handshakes for POST /api/catalog/secure; the encrypted payload is the same proxy payload plus the handshake csrfToken when CSRF is enabled.

If CATALOG_BEARER_TOKEN should authenticate against this API's JWT middleware, generate a deployment token locally and store it only in environment/secret storage:

CATALOG_BRIDGE_TOKEN_EXPIRES_IN=365d npm run bridge:token

Optional generator inputs are CATALOG_BRIDGE_TOKEN_SUB, CATALOG_BRIDGE_TOKEN_ROLE, and CATALOG_BRIDGE_TOKEN_EXPIRES_IN. Rotate this token like any other server-side credential.

PlayFab / Titles / OS

Variable Default Description
TITLE_ID 20CA2 Fallback TitleId (used if alias not provided)
DEFAULT_ALIAS prod Default alias → TitleId mapping (see /titles)
FEATURED_PRIMARY_ALIAS prod Primary alias for featured/SSE sources
OS iOS OS label used in upstream requests
PLAYFAB_DEVICE_ID generated Stable anonymous PlayFab device identity
PLAYFAB_CONCURRENCY 12 Parallel PlayFab requests
PLAYFAB_BATCH 600 Max batch size for bulk PlayFab calls

npm run setup generates PLAYFAB_DEVICE_ID once and preserves it on later setup runs. Keep the value stable per installation and outside version control so PlayFab reuses the linked anonymous account. Without it, the service uses a process-local fallback that changes after a restart.

Caching / TTLs / Sizes

Variable Default Description
SESSION_TTL_MS 1800000 Fallback TTL when PlayFab omits token expiration
SESSION_EXPIRY_SKEW_MS 60000 Refresh token this long before expiration
SESSION_CACHE_MAX 1000 Max entries in session cache
DATA_TTL_MS 300000 LRU generic data TTL (ms)
DATA_CACHE_MAX 20000 Max entries in generic data cache
ADV_SEARCH_TTL_MS 60000 TTL for advanced search cache (ms)
PAGE_SIZE 100 Default page size for list endpoints

Watchers & SSE

Variable Default Description
ENABLE_SALES_WATCHER true Enable sales watcher
ENABLE_ITEM_WATCHER true Enable item watcher
ENABLE_PRICE_WATCHER true Enable price watcher
ENABLE_TRENDING_WATCHER true Enable trending watcher
ENABLE_CREATOR_PARTNER_WATCHER true Enable creator/partner registry watcher
ENABLE_FEATURED_CONTENT_WATCHER true Enable featured content watcher
ENABLE_SUBSCRIPTION_WATCHER false Enable Marketplace Pass / Realms Plus membership watcher
SSE_HEARTBEAT_MS 15000 SSE heartbeat interval (min 5000)
SALES_WATCH_INTERVAL_MS 30000 Sales watcher interval
PRICE_WATCH_INTERVAL_MS 30000 Price watcher interval
ITEM_WATCH_INTERVAL_MS 30000 Item watcher interval
ITEM_WATCH_CREATED_LOOKBACK_MS 86400000 StartDate/CreationDate window for newly visible catalog items
FEATURED_CONTENT_WATCH_INTERVAL_MS 21600000 Featured content watcher interval
SUBSCRIPTION_WATCH_INTERVAL_MS 300000 Marketplace Pass / Realms Plus watcher interval
ITEM_WATCH_TOP 150 Items per page scanned in item watcher
ITEM_WATCH_PAGES 3 Pages scanned per cycle in item watcher
TRENDING_INTERVAL_MS 60000 Trending watcher interval
TRENDING_WINDOW_HOURS 24 Window for trending scoring
TRENDING_PAGE_TOP 200 Items per page scanned in trending watcher
TRENDING_PAGES 3 Pages scanned per cycle in trending watcher
TRENDING_TOP_N 20 Top creators emitted per trending window
CREATOR_PARTNER_WATCH_INTERVAL_MS 21600000 Creator/partner registry watcher interval
CREATORNAME_MODE nospace Creator registry normalization mode (nospace or alnum)
STORE_CONCURRENCY 6 Parallel store requests
PRICE_WATCH_MAX_STORES 50 Max stores scanned for price signature

Search / Enrichment

Variable Default Description
MAX_SEARCH_BATCHES 10 Batches for paginated search
FETCH_CONCURRENCY 5 Parallel catalog search pages
MAX_FETCH_BATCHES 20 Batches for “all items” fetch
ADV_SEARCH_BATCH 300 Batch size advanced search
ADV_SEARCH_MAX_BATCHES 10 Max batches advanced search
ADV_SEARCH_CONCURRENCY 5 Parallel advanced search pages
MARKETPLACE_SEARCH_ITEMS_TTL_MS 30000 Cache TTL for native SearchItems responses
MARKETPLACE_SEARCH_SUGGEST_TTL_MS 30000 Cache TTL for suggestions
MARKETPLACE_SEARCH_AUDIT_TTL_MS 60000 Cache TTL for search health audits
MULTILANG_ALL true Enrich via GetItems for all results
MULTILANG_ENRICH_BATCH 100 Requested GetItems batch size; PlayFab calls are capped at 50 IDs
MULTILANG_ENRICH_CONCURRENCY 5 GetItems concurrency
STORE_MAX_FOR_PRICE_ENRICH 500 Stores consulted per item (prices)
STORE_PRICE_TTL_MS 120000 Computed item store-price cache TTL (ms)

Reviews

Variable Default Description
REVIEWS_ENABLED true Enable review enrichment in item details
REVIEWS_FETCH_COUNT 20 Number of reviews to fetch per item (sample)

Webhooks

Variable Default Description
WEBHOOK_TIMEOUT_MS 5000 Upstream webhook timeout (ms)
WEBHOOK_MAX_RETRIES 3 Max webhook delivery retries
WEBHOOK_CONCURRENCY 4 Parallel webhook deliveries

Rate Limiting

Variable Default Description
RATE_LIMIT_ENABLED true Toggle rate limiting globally
RATE_LIMIT_DEFAULT_WINDOW_MS 60000 Default window for generic routes
RATE_LIMIT_DEFAULT_MAX 60 Default max requests per window
RATE_LIMIT_LOGIN_WINDOW_MS 900000 /login window (15 minutes)
RATE_LIMIT_LOGIN_MAX 20 Max login attempts per window
RATE_LIMIT_MARKETPLACE_WINDOW_MS 60000 Window for /marketplace routes
RATE_LIMIT_MARKETPLACE_MAX 120 Max /marketplace requests per window
RATE_LIMIT_EVENTS_WINDOW_MS 60000 Window for /events endpoints
RATE_LIMIT_EVENTS_MAX 120 Max /events requests per window
RATE_LIMIT_ADMIN_WINDOW_MS 60000 Window for /admin routes
RATE_LIMIT_ADMIN_MAX 60 Max /admin requests per window
RATE_LIMIT_HEALTH_WINDOW_MS 10000 Window for health endpoints
RATE_LIMIT_HEALTH_MAX 120 Max health checks per window
RATE_LIMIT_VERSION_WINDOW_MS 300000 Window for /version checks
RATE_LIMIT_VERSION_MAX 200 Max /version requests per window

Data Files & Local Storage

This service uses small JSON files under src/data/ which you should persist in production (bind mount / volume):

  • titles.json — alias → TitleId mapping. Example:

    {
      "production": { "id": "20CA2", "notes": "Production title" },
      "staging":  { "id": "AB123", "notes": "Staging" }
    }
  • creators.json — creator registry used for search resolution:

    [
      { "id": "creator-uuid", "creatorName": "SomeCreator", "displayName": "Some Creator" }
    ]
  • webhooks.json — persisted webhook registrations (auto-managed).

If these files are missing, the API will warn (e.g., creators) or initialize to empty structures.


Runtime & Architecture

Client ──► /login ──► JWT ─┐
                           │        ┌───────────────┐
(Bearer) ───────────────────┼──────► │ Express API   │
                           │        │  • Routes     │
                           │        │  • Middleware │
                           │        └─────┬─────────┘
                           │              │
                           │              ▼
                           │        ┌───────────────┐
                           │        │ Services      │
                           │        │  • marketplace│
                           │        │  • advanced   │
                           │        │  • watchers   │
                           │        └─────┬─────────┘
                           │              │
                           │              ▼
                           │        ┌───────────────┐
                           │        │ utils/playfab │──► PlayFab API
                           │        │ (Axios, Retry │
                           │        │  Keep-Alive)  │
                           │        └───────────────┘
                           │
SSE ◄────────────────────────── EventBus + Watchers (sales/items/prices/trending/creator partners)
Webhooks ◄───────────────────── WebhookService (signature, retry/backoff)
Cache  ◄─────────────────────── LRU (sessions/data) + getOrSetAsync de-dup

Highlights

  • Central sendPlayFabRequest() adds retries, jittered backoff, and 429 handling using Retry-After when present.
  • LRU session cache ensures minimal auth churn (SessionTicket + EntityToken).
  • ETag middleware serializes handler results and computes weak ETags (W/"<hex>-<sha1-16>").

Security Model

  • Auth: All routes require Authorization: Bearer <jwt> except /login, /openapi.json, /docs (when enabled), and GET /marketplace/mc-token.

  • Roles: role=admin is required for:

    • GET /session/:alias
    • POST /webhooks
  • Input validation: express-validator on most routes.

  • Helmet: baseline HTTP protection; CSP disabled for Swagger UI compatibility.

  • CORS: enabled for http://localhost:5173 and https://view-marketplace.net by default; override via CORS_ORIGINS (comma-separated).


HTTP API

Authentication & Tokens

POST /login Body:

{ "username": "<ADMIN_USER>", "password": "<ADMIN_PASS>" }

Response:

{ "token": "<jwt>" }

The token encodes { role: "admin" } for admin credentials. Use it via Authorization: Bearer <jwt>.

GET /marketplace/mc-token Returns a Minecraft Services token for the configured primary title (FEATURED_PRIMARY_ALIASDEFAULT_ALIASTITLE_ID). !!!No Auth Token required!!!.

Response:

{ "titleId": "20CA2", "token": "<mc-token>" }

Global Conventions

  • Content-Type: application/json; charset=utf-8

  • ETags for GETs; send If-None-Match to leverage 304 Not Modified.

  • Pagination opt-in:

    • page (≥1), pageSize (1..100)
    • or skip and limit (1..1000)
  • Response pagination headers:

    • X-Total-Count
    • Content-Range: items <start>-<end>/<total>

Routes Summary

System & Docs

Method Path Description Auth
GET / Service banner/health
GET /health Runtime health report
GET /version GitHub update status
GET /openapi.json OpenAPI spec
GET /docs Swagger UI (when enabled)

Auth & Admin

Method Path Description Role
POST /login Issue JWT
GET /session/:alias Show PlayFab session admin
POST /webhooks Register webhooks admin

Titles & Creators

Method Path Description
GET /titles All alias→TitleId entries
POST /titles Create alias { alias, id, notes? }
DELETE /titles/:alias Remove alias
GET /creators List creatorName, displayName

Marketplace Catalog

Method Path Description
GET /marketplace/all/:alias Entire catalog (optionally ?tag=...)
GET /marketplace/latest/:alias Latest items (?count<=50)
GET /marketplace/popular/:alias Popular by rating/totalcount
GET /marketplace/free/:alias Free items
GET /marketplace/marketplace-pass/:alias Marketplace Pass items (csb)
GET /marketplace/realms-plus/:alias Realms Plus items (realms_plus)
GET /marketplace/tag/:alias/:tag Filter by tag
GET /marketplace/search/:alias Keyword full-text search
POST /marketplace/search/items/:alias Native SearchItems with cursor pagination
POST /marketplace/search/store/:alias Store-scoped SearchItems
GET /marketplace/search/suggest/:alias Lightweight search suggestions
POST /marketplace/search/localized/:alias Multi-language search comparison
POST /marketplace/search/audit/:alias Search-based content health scan
GET /marketplace/details/:alias/:itemId Item details (optional enrichments)
GET /marketplace/friendly/:alias/:friendlyId Resolve by FriendlyId
GET /marketplace/resolve/:alias/:itemId Resolve by ItemId (with references)
POST /marketplace/resolve/batch/:alias Resolve up to 50 ids/alternateIds
GET /marketplace/resolve/friendly/:alias/:friendlyId Resolve FriendlyId → item (+refs)
GET /marketplace/summary/:alias Compact list (id/title/links)
GET /marketplace/compare/:creatorName Compare a creator across titles
GET /marketplace/featured-servers Curated featured servers
GET /marketplace/featured-content Featured persona items
GET /marketplace/mc-token MC token for primary title
GET /marketplace/sales Aggregated sales across aliases
GET /marketplace/sales/:alias Aggregated sales for one alias
POST /marketplace/search/advanced/:alias Advanced search + facets or cursor mode

Routes — Detailed Reference

Below, TOKEN refers to the JWT from /login.

GET /version

Returns the detected local version, local Git metadata, configured GitHub repository, latest release or tag metadata, and whether an update is available. Local SemVer Git tags are preferred over package.json; when the deployed commit matches the latest GitHub release/tag commit, the response reports the service as current even if package.json still contains an older fallback version.

curl -s "http://localhost:3000/version" -H "Authorization: Bearer $TOKEN"

Optional query parameters:

  • refresh=true: bypass the local update-check cache and revalidate against GitHub.
  • includePrerelease=true: include GitHub prereleases when checking releases.
  • source=auto|release|tag: choose the remote source. auto checks the latest release and falls back to tags when no release exists.

Example response:

{
  "versionCheckerVersion": "v1",
  "local": {
    "name": "playfab-bedrock-catalog",
    "version": "1.0.0",
    "git": { "branch": "main", "shortCommit": "abc123def456" }
  },
  "repository": {
    "fullName": "Daniel-Ric/PlayFab-Catalog-Service-Bedrock",
    "htmlUrl": "https://github.com/Daniel-Ric/PlayFab-Catalog-Service-Bedrock"
  },
  "remote": {
    "status": "ok",
    "source": "release",
    "latest": { "version": "1.1.0", "tagName": "v1.1.0" }
  },
  "update": {
    "available": true,
    "status": "outdated",
    "current": "1.0.0",
    "latest": "1.1.0"
  }
}

GET /titles

Returns all known aliases with notes.

curl -s "http://localhost:3000/titles" -H "Authorization: Bearer $TOKEN"

POST /titles

Create/update an alias → TitleId.

curl -s -X POST "http://localhost:3000/titles" \
 -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
 -d '{ "alias":"my-title", "id":"20CA2", "notes":"Production" }'

DELETE /titles/:alias

Remove an alias mapping.

GET /creators

Paginated list of creators (from src/data/creators.json).

GET /marketplace/all/:alias

All catalog items for an alias, optional ?tag=.... Supports server-side pagination. ResolvedReferences are only included with ?expand=refs.

GET /marketplace/latest/:alias?count=10

Latest items (max 50).

GET /marketplace/popular/:alias

Sorted by rating/totalcount desc.

GET /marketplace/free/:alias

Items with displayProperties/price = 0.

GET /marketplace/marketplace-pass/:alias

Items with the csb tag. Supports creator, date, pagination, and orderBy filters.

GET /marketplace/realms-plus/:alias

Items with the realms_plus tag. Supports creator, date, pagination, and orderBy filters.

GET /marketplace/tag/:alias/:tag

Filter by tag; fully enriched items (optionally with references).

GET /marketplace/search/:alias?keyword=<q>

Runs a PlayFab Catalog full-text search across the selected title alias. creatorName=<name> is optional and, when present, is resolved via creators.json (matches creatorName or displayName, whitespace-insensitive).

POST /marketplace/search/items/:alias

Native Catalog/SearchItems wrapper with cursor pagination. Use this for efficient frontend search when you need PlayFab Language, Select, Store, Count, and ContinuationToken support.

{
  "search": "dragon",
  "filter": "ContentType eq '3PServerContent_V1.2'",
  "orderBy": "StartDate desc",
  "select": "title,description,images,priceOptions",
  "language": "de-DE",
  "count": 24,
  "continuationToken": ""
}

Response shape:

{
  "items": [{ "id": "ItemId", "title": "Title", "thumbnail": "https://...", "price": { "amount": 660, "currencyId": "Minecoins" } }],
  "pagination": { "count": 24, "requestedCount": 24, "continuationToken": "...", "hasNext": true },
  "meta": { "source": "playfab.catalog.searchItems", "language": "de-DE" }
}

POST /marketplace/search/store/:alias

Same response shape as /marketplace/search/items/:alias, but SearchItems runs in a PlayFab store context. Provide one of:

{ "storeId": "store-id", "search": "", "count": 24 }

or:

{ "storeAlternateId": { "type": "FriendlyId", "value": "store-friendly-id" }, "count": 24 }

GET /marketplace/search/suggest/:alias?q=<text>

Returns lightweight item, creator, and keyword suggestions. Optional query params: language, filter, count (1..20).

POST /marketplace/search/localized/:alias

Runs the same SearchItems request for multiple languages and returns one result bucket per language:

{
  "search": "dragon",
  "languages": ["de-DE", "en-US"],
  "select": "title,description,keywords,images",
  "count": 10
}

POST /marketplace/search/audit/:alias

Search-based content health scan. It checks the returned SearchItems pages for missing thumbnails, screenshots, contents, platforms, deep links, requested localization texts, malformed URLs, and invalid min/max client version ranges.

{
  "filter": "ContentType eq '3PServerContent_V1.2'",
  "languages": ["de-DE", "en-US"],
  "count": 50,
  "maxPages": 3
}

POST /marketplace/resolve/batch/:alias

Resolves up to 50 item IDs or alternate IDs with Catalog/GetItems in one request:

{
  "ids": ["item-id-1", "item-id-2"],
  "alternateIds": [{ "type": "FriendlyId", "value": "friendly-id" }]
}

Content type and date filters on search endpoints

Search endpoints accept contentType=<PlayFab ContentType> for exact PlayFab ContentType matches, for example contentType=3PServerContent_V1.2. Item list/search endpoints also accept ISO date ranges for PlayFab catalog timestamps: creationDateFrom/creationDateTo, lastModifiedDateFrom/lastModifiedDateTo, and startDateFrom/startDateTo. Date filters are pushed into PlayFab search where possible and can be combined with pagination, creator, tag, free, popular, latest, summary, compare, player marketplace search, recommendations, and sales item queries.

GET /marketplace/details/:alias/:itemId?expand=prices,reviews,refs

  • prices: fetch store prices for the item (bounded by STORE_MAX_FOR_PRICE_ENRICH).
  • reviews: include GetItemReviewSummary + GetItemReviews sample (REVIEWS_FETCH_COUNT).
  • refs: resolve ItemReferences to full items and return under ResolvedReferences.

GET /marketplace/friendly/:alias/:friendlyId

Get a single item by its FriendlyId (via a search), then fully resolve + references.

GET /marketplace/resolve/:alias/:itemId

Resolve an item by Id; will include ResolvedReferences.

GET /marketplace/resolve/friendly/:alias/:friendlyId

Same as friendly, explicit route for clarity.

GET /marketplace/summary/:alias

Compact list for link rendering:

{ "id": "ItemId", "title": "Neutral Title", "detailsUrl": "...", "clientUrl": "..." }

GET /marketplace/compare/:creatorName

Compare one creator across all configured titles in titles.json. Returns { [alias]: items[] }.

GET /marketplace/featured-servers

Returns a curated list from src/config/featuredServers.js, each resolved to a live item (if present).

GET /marketplace/featured-content

Returns featured persona items from the Dressing Room layout page.

GET /marketplace/mc-token

Returns the Minecraft Services authorization token for the configured primary title.

GET /marketplace/sales and /marketplace/sales/:alias

Aggregate store sales (from SearchStores + GetStoreItems) with items resolved. Optional ?creator=<displayName> filters to a single creator. Returns:

{
  "totalItems": 123,
  "itemsPerCreator": { "Creator A": 10, "Creator B": 5 },
  "sales": {
    "storeId": {
      "id": "storeId",
      "title": "Sale Title",
      "discountPercent": 30,
      "startDate": "2024-10-01T00:00:00Z",
      "endDate": "2024-10-08T00:00:00Z",
      "items": [ { "id": "...", "rawItem": { /* full item */ } } ]
    }
  }
}

POST /marketplace/search/advanced/:alias

Advanced Search uses a strict, fixed request body. Default mode: "page" keeps the existing paged/faceted behavior with query, filters, and sort. mode: "cursor" uses native Catalog/SearchItems and accepts language, select, store, storeId, storeAlternateId, count, and continuationToken. Unknown fields are rejected with 400 Bad Request.

{
  "query": {
    "text": "valentine bundle"
  },
  "filters": {
    "id": "6656d5d4-d8ad-49a5-870f-04cb26d3ce26",
    "friendlyId": "f5c30f04-e47e-4a2c-88f1-313b868195dc",
    "alternateIds": [{ "type": "FriendlyId", "value": "f5c30f04-e47e-4a2c-88f1-313b868195dc" }],
    "keywords": ["bundle"],
    "isStackable": false,
    "platforms": ["android.googleplay"],
    "tags": ["hidden_offer", "3P"],
    "tagsAny": ["3P", "server"],
    "tagsAll": ["marketplace", "hidden_offer"],
    "contentType": "3PServerContent_V1.2",
    "contentKinds": ["world"],
    "creationDate": { "from": "2026-02-01T00:00:00Z", "to": "2026-02-28T23:59:59Z" },
    "lastModifiedDate": { "from": "2026-02-01T00:00:00Z", "to": "2026-02-28T23:59:59Z" },
    "startDate": { "from": "2026-02-01T00:00:00Z", "to": "2026-02-28T23:59:59Z" },
    "priceAmounts": { "min": 1000, "max": 1500, "currencyId": "ecd19d3c-7635-402c-a185-eb11cb6c6946" },
    "creatorName": "The Hive",
    "offerId": "f5c30f04-e47e-4a2c-88f1-313b868195dc",
    "purchasable": true,
    "packIdentityType": "durable",
    "ratingMin": 4.0
  },
  "sort": [
    { "field": "startDate", "dir": "desc" },
    { "field": "priceAmount", "dir": "asc" }
  ]
}

Cursor mode example:

{
  "mode": "cursor",
  "query": { "text": "dragon" },
  "filters": { "contentType": "3PServerContent_V1.2" },
  "language": "de-DE",
  "select": "title,description,images,priceOptions",
  "count": 24,
  "continuationToken": ""
}

Query behavior

  • query.text is forwarded to PlayFab Search (fuzzy/full-text; e.g., matches via title/description/indexed fields).

Filter behavior (PlayFab vs API layer)

The following filters are pushed directly to PlayFab OData in Catalog/Search:

  • id
  • friendlyId, alternateIds
  • isStackable
  • platforms
  • tags, tagsAny, tagsAll
  • contentType, contentTypes
  • contentKinds (skinpack, world, persona, addon, resourcepack, mashup, server)
  • creationDate, lastModifiedDate, startDate
  • priceAmounts.min/max (via DisplayProperties/price)
  • ratingMin (via rating/average)
  • creatorName
  • purchasable

The following filters are additionally evaluated in the API layer (when PlayFab cannot reliably cover them directly):

  • keywords
  • offerId
  • packIdentityType
  • priceAmounts, including optional currencyId checks via Price/PriceOptions.

Sorting

Allowed sort fields:

  • id, type, title, description, keywords
  • contentType, isStackable, platforms, tags
  • creationDate, lastModifiedDate, startDate
  • priceAmount, creatorName, purchasable, packIdentityType

Not allowed (returns 400):

  • offerId
  • friendlyId
  • alternateIds

Performance note

  • The service still uses paginated PlayFab batches (ADV_SEARCH_BATCH, ADV_SEARCH_MAX_BATCHES) and applies local filtering/sorting only after upstream fetch when required.

Events (SSE)

  • /events/stream (optional query events=<comma-separated-event-names>): item.snapshot, item.created, item.updated, marketplace.pass.snapshot, marketplace.pass.added, marketplace.pass.removed, marketplace.pass.updated, realms.plus.snapshot, realms.plus.added, realms.plus.removed, realms.plus.updated, sale.snapshot, sale.update, price.changed, creator.trending, featured.content.added, featured.content.removed, featured.content.updated.
  • Subscription events compare the current csb / realms_plus tag memberships with the last persisted watcher state. Added/removed events include the current item list, updated events include before / after entries, and each item includes subscription.startDate / subscription.endDate from csbStartDate, csbEndDate, realmsPlusStartDate, and realmsPlusEndDate.
  • Featured content changes emit dedicated featured.content.added, featured.content.removed, and featured.content.updated events. Payloads include the matching addedItems, removedItems, or changedItems plus item details with featuredContext (page, row, component, itemIndex); currentItemDetails and previousItemDetails provide the full after/before featured item lists. Same-ID layout or metadata changes emit featured.content.updated, set contentChanged=true, and include previousContentSignature / currentContentSignature.

Admin Webhooks

  • POST /webhooks — register { events, url, secret? }

Usage Examples (cURL)

Login

curl -sS -X POST http://localhost:3000/login \
  -H "Content-Type: application/json" \
  -d '{"username":"'"$ADMIN_USER"'","password":"'"$ADMIN_PASS"'"}'

Featured servers

TOKEN="<jwt>"
curl -sS "http://localhost:3000/marketplace/featured-servers" \
  -H "Authorization: Bearer $TOKEN"

Featured content

TOKEN="<jwt>"
curl -sS "http://localhost:3000/marketplace/featured-content" \
  -H "Authorization: Bearer $TOKEN"

Compare a creator across titles

curl -sS "http://localhost:3000/marketplace/compare/SomeCreator" \
  -H "Authorization: Bearer $TOKEN"

Resolve by FriendlyId

curl -sS "http://localhost:3000/marketplace/resolve/friendly/my-alias/FRIENDLY_ID" \
  -H "Authorization: Bearer $TOKEN"

Sales (filtered by creator display name)

curl -sS "http://localhost:3000/marketplace/sales?creator=Some%20Creator" \
  -H "Authorization: Bearer $TOKEN"

SSE subscription (Node)

import EventSource from "eventsource";
const es = new EventSource("http://localhost:3000/events/stream?events=creator.trending", {
  headers: { Authorization: `Bearer ${token}` }
});
es.addEventListener("creator.trending", e => console.log(JSON.parse(e.data)));
es.addEventListener("ping", () => {});

Pagination & Caching (ETag/Cache-Control)

  • Opt-in pagination: pass page/pageSize or skip/limit.

  • Lightweight list responses: pass full=false (or enrich=false) to skip full GetItems enrichment, and refs=false to skip resolving ItemReferences on list endpoints that include them by default.

  • Headers:

    • X-Total-Count
    • Content-Range: items <start>-<end>/<total>
  • ETag: Responses wrapped by withETag() set a weak ETag; if If-None-Match matches the current tag, the server returns 304 without a body.

  • Cache-Control on most GET routes:

    • Example: public, max-age=60, s-maxage=300, stale-while-revalidate=600

Error Model

{
  "error": {
    "type": "bad_request | unauthorized | forbidden | not_found | internal_error",
    "message": "Human readable message",
    "details": [ /* express-validator issues, optional */ ],
    "traceId": "request correlation id"
  }
}
  • The traceId mirrors X-Request-Id if provided, else a short random id.
  • 4xx/5xx are normalized; in production 5xx messages become Internal server error.

Rate Limiting

Rate limiting is controlled via the RATE_LIMIT_* environment variables.

  • When RATE_LIMIT_ENABLED=true:

    • /login uses an express-rate-limit instance that, by default, allows 20 requests per 15 minutes (configurable via RATE_LIMIT_LOGIN_WINDOW_MS and RATE_LIMIT_LOGIN_MAX).
    • Additional per-group limiters apply to marketplace, events, admin and health endpoints, using their respective RATE_LIMIT_* envs.
  • Violations return 429 Too Many Requests with a short JSON error.


Server-Sent Events (SSE)

  • Endpoints:

    • /events/stream
  • Each stream sets required headers, flushes, and pings every SSE_HEARTBEAT_MS (min 5000).

  • Reconnect strategy: clients should auto-reconnect; no Last-Event-ID is used.

  • Backpressure: events are small JSON payloads; consumers should be idempotent.

  • Watchers are toggled with:

    • ENABLE_ITEM_WATCHER, ENABLE_PRICE_WATCHER, ENABLE_SALES_WATCHER, ENABLE_TRENDING_WATCHER, ENABLE_CREATOR_PARTNER_WATCHER, ENABLE_FEATURED_CONTENT_WATCHER, ENABLE_SUBSCRIPTION_WATCHER.

Webhooks

Register

curl -sS -X POST http://localhost:3000/webhooks \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"event":"price.changed","url":"https://example.com/hook","secret":"<optional-256-max>"}'

Events: sale.update, item.snapshot, item.created, item.updated, marketplace.pass.snapshot, marketplace.pass.added, marketplace.pass.removed, marketplace.pass.updated, realms.plus.snapshot, realms.plus.added, realms.plus.removed, realms.plus.updated, price.changed, creator.trending, featured.content.added, featured.content.removed, featured.content.updated

Delivery

  • JSON body: { "event": "<name>", "ts": 1730000000000, "payload": { ... } }
  • Header: X-Webhook-Signature: <sha1> where the value is sha1(stableStringify({ body, secret }))
  • Retries: up to WEBHOOK_MAX_RETRIES with exponential backoff + jitter
  • Concurrency: WEBHOOK_CONCURRENCY (default 4)
  • Timeout per delivery: WEBHOOK_TIMEOUT_MS (default 5000 ms)

Verify signature (Node)

import crypto from "crypto";
function stableStringify(obj){ if(obj===null||typeof obj!=="object") return JSON.stringify(obj);
  if(Array.isArray(obj)) return "["+obj.map(stableStringify).join(",")+"]";
  const keys=Object.keys(obj).sort(); return "{"+keys.map(k=>JSON.stringify(k)+":"+stableStringify(obj[k])).join(",")+"}";
}
function verify(sig, body, secret){
  const expect = crypto.createHash("sha1").update(stableStringify({ body, secret })).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expect));
}

Verify signature (Python)

import hashlib, hmac, json
def stable(o):
    if isinstance(o, dict):
        return "{" + ",".join(f"{json.dumps(k)}:{stable(o[k])}" for k in sorted(o)) + "}"
    if isinstance(o, list):
        return "[" + ",".join(stable(x) for x in o) + "]"
    return json.dumps(o)
def verify(sig, body, secret):
    expect = hashlib.sha1(stable({"body": body, "secret": secret}).encode()).hexdigest()
    return hmac.compare_digest(sig, expect)

OpenAPI & Swagger UI

  • Spec: GET /openapi.json (always available)
  • Swagger UI: GET /docs (when ENABLE_DOCS=true)

Note

/openapi.json is always exposed, but the interactive Swagger UI at /docs is disabled by default. If you enable docs in a public environment, make sure the rest of your deployment and authentication settings match your intended exposure model.

Spec composition

  • Base: src/docs/openapi-base.yaml
  • Schemas: src/docs/schemas/**/*.yaml
  • Paths: src/docs/paths/**/*.yaml
  • Builder: config/swagger.js merges all YAML parts

Validator

  • express-openapi-validator plugged when VALIDATE_REQUESTS=true (and optionally VALIDATE_RESPONSES=true).
  • Custom BearerAuth handler verifies JWT or throws standardized 401/403 early.

Logging

  • Winston logger with colorized console (chalk).
  • middleware/requestLogger prints and lines at debug level including latency.
  • Upstream calls log store counts, discount percents, item totals under debug to aid diagnosis.

Caching Layers

  • LRU (sessionCache) — PlayFab login session (SessionTicket, EntityToken), soft TTL with refresh via getSession().
  • LRU (dataCache) — Generic results via getOrSetAsync(key, fn, ttlOverride?) to deduplicate in-flight calls.
  • ETag — Response entity tagging on controllers using withETag(handler).
  • Cache headers — Per-route Cache-Control hints via cacheHeaders(seconds, smax) in index.js.

Data Model (Transformed Item)

Items are normalized by utils/playfab.transformItem():

type TransformedImage = {
  Id: string;
  Tag: string;
  Type: "Thumbnail" | "Screenshot";
  Url: string;
};
type TransformedItem = {
  Id: string;
  Title: Record<string,string>;
  Description?: Record<string,string>;
  ContentType?: string;
  Tags?: string[];
  Platforms?: string[];
  Images: TransformedImage[]; // thumbnails first
  StartDate?: string;         // normalized from start/creation date
  DisplayProperties?: any;
  ResolvedReferences?: TransformedItem[]; // when expanded
  StorePrices?: Array<{ storeId: string; storeTitle: any; amounts: Array<{ currencyId: string; amount: number }> }>;
  Reviews?: { summary: any; reviews: any[] };
};

Directory Layout

src/
  config/                 # caches, logger, rate limiter, swagger builder
  controllers/            # http handlers (marketplace, events, admin, etc.)
  middleware/             # etag, pagination, validators, request logging, sse heartbeat
  routes/                 # express routers per domain
  services/               # business logic, watchers, webhook service, event bus
  utils/                  # playfab client, titles/creators db, filter, pagination, hashing, storage
  scripts/                # OpenAPI ref fixer
  index.js                # Express bootstrap

Development & Utilities

OpenAPI reference fixer

Normalize schema refs / nullable patterns across docs/:

node src/scripts/fix-openapi-refs.js

Enable request validation

VALIDATE_REQUESTS=true ENABLE_DOCS=true node src/index.js

Verbose upstream diagnostics

LOG_LEVEL=debug node src/index.js

Sample .env

PORT=3000
NODE_ENV=production
JWT_SECRET=please-change-me-to-a-very-long-random-string-32plus
ADMIN_USER=admin
ADMIN_PASS=change-me

DEFAULT_ALIAS=prod
TITLE_ID=20CA2
OS=iOS
PLAYFAB_DEVICE_ID=vmc-<unique-random-id>
TRUST_PROXY=1
LOG_LEVEL=info
CORS_ORIGINS=http://localhost:5173,https://view-marketplace.net

HTTP_MAX_SOCKETS=512
HTTPS_MAX_SOCKETS=512
UPSTREAM_TIMEOUT_MS=20000
RETRY_BUDGET=3

SESSION_TTL_MS=1800000
SESSION_EXPIRY_SKEW_MS=60000
SESSION_CACHE_MAX=1000
DATA_CACHE_MAX=20000
DATA_TTL_MS=300000

FEATURED_PRIMARY_ALIAS=prod

MULTILANG_ALL=true
MULTILANG_ENRICH_BATCH=100
MULTILANG_ENRICH_CONCURRENCY=5
STORE_CONCURRENCY=6
STORE_MAX_FOR_PRICE_ENRICH=500

VALIDATE_REQUESTS=false
VALIDATE_RESPONSES=false
ENABLE_DOCS=false

UPDATE_CHECK_ENABLED=true
UPDATE_CHECK_REPOSITORY=Daniel-Ric/PlayFab-Catalog-Service-Bedrock
UPDATE_CHECK_TTL_MS=900000
UPDATE_CHECK_TIMEOUT_MS=5000
UPDATE_CHECK_STARTUP_LOG=true

PAGE_SIZE=100
REVIEWS_ENABLED=true
REVIEWS_FETCH_COUNT=20

ENABLE_SALES_WATCHER=true
SALES_WATCH_INTERVAL_MS=30000
ENABLE_ITEM_WATCHER=true
ITEM_WATCH_INTERVAL_MS=30000
ITEM_WATCH_TOP=150
ITEM_WATCH_PAGES=3
ENABLE_PRICE_WATCHER=true
PRICE_WATCH_INTERVAL_MS=30000
PRICE_WATCH_MAX_STORES=50
ENABLE_TRENDING_WATCHER=true
TRENDING_INTERVAL_MS=60000
TRENDING_WINDOW_HOURS=24
TRENDING_PAGE_TOP=200
TRENDING_PAGES=3
TRENDING_TOP_N=20
ENABLE_CREATOR_PARTNER_WATCHER=true
CREATOR_PARTNER_WATCH_INTERVAL_MS=21600000
CREATORNAME_MODE=nospace
ENABLE_FEATURED_CONTENT_WATCHER=true
FEATURED_CONTENT_WATCH_INTERVAL_MS=21600000
ENABLE_SUBSCRIPTION_WATCHER=false
SUBSCRIPTION_WATCH_INTERVAL_MS=300000
SSE_HEARTBEAT_MS=15000

ADV_SEARCH_TTL_MS=60000
ADV_SEARCH_BATCH=300
ADV_SEARCH_MAX_BATCHES=10
ADV_SEARCH_CONCURRENCY=5

WEBHOOK_TIMEOUT_MS=5000
WEBHOOK_MAX_RETRIES=3
WEBHOOK_CONCURRENCY=4

MAX_SEARCH_BATCHES=10
MAX_FETCH_BATCHES=20
FETCH_CONCURRENCY=5
PLAYFAB_CONCURRENCY=12
PLAYFAB_BATCH=600

RATE_LIMIT_ENABLED=true
RATE_LIMIT_DEFAULT_WINDOW_MS=60000
RATE_LIMIT_DEFAULT_MAX=60

RATE_LIMIT_LOGIN_WINDOW_MS=900000
RATE_LIMIT_LOGIN_MAX=20

RATE_LIMIT_MARKETPLACE_WINDOW_MS=60000
RATE_LIMIT_MARKETPLACE_MAX=120

RATE_LIMIT_EVENTS_WINDOW_MS=60000
RATE_LIMIT_EVENTS_MAX=120

RATE_LIMIT_ADMIN_WINDOW_MS=60000
RATE_LIMIT_ADMIN_MAX=60

RATE_LIMIT_HEALTH_WINDOW_MS=10000
RATE_LIMIT_HEALTH_MAX=120

RATE_LIMIT_VERSION_WINDOW_MS=300000
RATE_LIMIT_VERSION_MAX=200

Testing

  • Unit: isolate pure helpers (utils/pagination, utils/hash, services/advancedSearchService).
  • Contract: with VALIDATE_REQUESTS=true, focus on validator test cases and unified error shapes.
  • SSE: simulate via eventsource and assert event names/payload shapes.
  • Webhooks: spin up a local receiver and validate signature & retry semantics.

Performance Tuning

  • Increase HTTP_MAX_SOCKETS / HTTPS_MAX_SOCKETS for higher parallelism.
  • Adjust STORE_CONCURRENCY, ADV_SEARCH_BATCH, and watcher intervals to respect upstream quotas.
  • Use CDN in front; the API already sends ETags and cache directives.
  • Prefer page/pageSize for stable pagination; skip/limit is supported for flexibility.
  • Enable LOG_LEVEL=debug temporarily to inspect store/item volumes and discounts.

Deployment

Docker

Dockerfile (example)

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node","src/index.js"]

docker-compose

version: "3.8"
services:
  api:
    build: .
    ports: ["3000:3000"]
    environment:
      PORT: 3000
      JWT_SECRET: ${JWT_SECRET}
      ADMIN_USER: ${ADMIN_USER}
      ADMIN_PASS: ${ADMIN_PASS}
      ENABLE_DOCS: "true"
      LOG_LEVEL: "info"
    volumes:
      - ./src/data:/app/src/data

Kubernetes (snippet)

apiVersion: apps/v1
kind: Deployment
metadata: { name: playfab-catalog-api }
spec:
  replicas: 2
  selector: { matchLabels: { app: playfab-catalog-api } }
  template:
    metadata: { labels: { app: playfab-catalog-api } }
    spec:
      containers:
        - name: api
          image: ghcr.io/org/playfab-catalog-api:latest
          ports: [{ containerPort: 3000 }]
          envFrom: [{ secretRef: { name: api-secrets } }]
          readinessProbe: { httpGet: { path: "/", port: 3000 }, initialDelaySeconds: 5, periodSeconds: 10 }
          livenessProbe:  { httpGet: { path: "/", port: 3000 }, initialDelaySeconds: 10, periodSeconds: 20 }
---
apiVersion: v1
kind: Service
metadata: { name: playfab-catalog-api }
spec:
  selector: { app: playfab-catalog-api }
  ports: [{ port: 80, targetPort: 3000 }]

Reverse Proxy (Nginx)

server {
  listen 80;
  server_name api.example.com;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 300s; # allow SSE
  }
}

If CATALOG_BRIDGE_ENABLED=true, a reverse proxy can additionally route these paths to the same Node service:

/api/security/csrf
/api/security/catalog-handshake
/api/catalog/proxy
/api/catalog/secure

Existing public /catalog/... proxy rules can remain unchanged. The bridge accepts only relative /catalog/... targets and forwards them to the configured CATALOG_UPSTREAM_ORIGIN.


Observability & Ops

  • Traceability: include X-Request-Id on requests; error payloads echo traceId.
  • SSE stability: ensure proxy timeouts ≥ heartbeat; enable TCP keep-alive.
  • Disk: persist src/data/*.json to retain titles, creators, and webhook registrations.
  • Metrics: wrap winston logs with your log shipping/metrics pipeline.

Troubleshooting

  • 401/403: Missing/invalid JWT or insufficient role.

  • 404:

    • Alias '<alias>' not found. → add to titles.json.
    • Creator '<name>' not found. → add to/verify creators.json.
    • Item not found → ensure correct alias/title and item id/friendly id.
  • Empty sales: no stores returned or store items not resolvable; turn on LOG_LEVEL=debug.

  • 429/5xx upstream: the client already retries with jitter; consider lowering concurrency/intervals.

  • SSE disconnects: check proxy idle timeout; heartbeats are at least 5s.


FAQ

How do I map aliases to TitleIds? Use POST /titles or edit src/data/titles.json and restart (hot reload reads mtime).

What’s the difference between resolve and friendly routes? Both end up returning a fully enriched item; friendly uses a search by alternateIds first.

Can I get facets for tags/creators? Yes — use advanced search; the response adds facets.tags, facets.creators, facets.contentTypes, and price buckets.

Are images always present? Items are filtered by isValidItem() to ensure at least one image; thumbnails are prioritized in Images.


Contributing

  1. Fork and create a feature branch.
  2. Add tests for your logic where applicable.
  3. Keep code style consistent; prefer small, readable modules.
  4. Update README/OpenAPI when you change public behavior.
  5. Open a PR with a clear description and screenshots/logs for UX/API changes.

License

This project integrates third-party services (PlayFab). Ensure compliance with your internal policies and the provider’s terms of use.

About

High-performance PlayFab Bedrock Marketplace API: browse, analyze, compare, and monitor creators, items, prices, sales, and trends in real time.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

7 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors