Frequently Asked Questions
Updated 2026-09-18: SDK installation and game-only embedding are separate deliverables. Implement the game-only embed guide, then register its deployed URL. A homepage with SDK installed can still overflow inside the player.
We've collected the TAP (TokenApps Points) where integrations actually get stuck. If your question isn't here, send it to cs@tokenpost.kr — you'll get an answer, and it will be added to this document as well.
Integration
Do I have to integrate the SDK to list my game?
No. Registration, exposure, play, and playtime measurement all work without the SDK. Measurement is performed by the platform's player page outside the game, so no game-side code is needed at all.
What the SDK unlocks are the next steps: play-based TAP accrual, account saves, rewarded ads, the continue sheet, and selling in-game items. Pick only the ones you need — the minimal integration is one script tag plus two function calls. Just follow the "5-minute integration" section in Getting Started.
Does the SDK throw errors when the game opens outside TokenApps, on our own site?
No. The SDK only operates when a parent window (the TokenApps player) is
present; without one it stays quietly idle. Calling ready() or track()
simply does nothing — no errors occur.
This means you can deploy the same build to your own site and TokenApps at
the same time. There is no need to add branching code that checks "was this
opened in TokenApps?" If you do want to tell the two apart, you can — by
whether a session message arrives. If one arrived, you're inside TokenApps.
We have a Unity / Godot / Cocos WebGL build and including v1.js is difficult.
The SDK file is only a convenience layer wrapped around the postMessage protocol, so if your engine can handle postMessage directly, implementing the protocol without the SDK behaves exactly the same. The message envelope always takes this form:
{ "__tokenapps": 1, "v": 1, "type": "ready", "payload": {} }
The full message table (12 types in both directions) and the REST spec are in
Specification §4–6. For Unity, instead of
Application.ExternalCall, we recommend calling
window.parent.postMessage(...) from a jslib plugin, receiving responses with
window.addEventListener("message", ...), and forwarding them to your game
objects with SendMessage.
Where do I test?
You can test on the real play page (tokenapps.io/app/<slug>/play)
immediately after submission → approval (Basic). The Basic stage is reachable
by direct link, so you can use it as-is for your team's internal QA. If you
want to verify your integration before approval, the source of the
reference game is a working example of the entire
contract — you can carry the same calls over verbatim.
Do I install the SDK from npm?
No — one script tag is the official install:
<script src="https://tokenapps.io/sdk/v1.js"></script>
No build tool, bundler, or package manager is involved, the v1 path is
backward-compatible, and it always serves the latest v1.x (updates require no
work on your side). A single static HTML game and a Vite/React project
integrate the same way. In bundler projects, use window.TokenApps directly
— if you want types, copy the TypeScript block from the
specification.
The repository contains v1.4 types and a loader in packages/sdk. As of 2026-09-17 it is not published on public npm. Use the CDN script or consume the repository package locally.
Can I integrate with an AI coding tool? (MCP)
We recommend it. If you are building your game with Claude Code, Cursor, or a similar tool, connect the TokenApps docs MCP server instead of pasting documentation:
claude mcp add --transport http tokenapps https://tokenapps.io/api/mcp
Once connected, the AI can read the spec, guide, and FAQ directly
(list_docs → read_doc) and search them (search_docs) — close to a
one-sentence "integrate the TokenApps SDK" experience. The server is
read-only and needs no authentication.
For tools without MCP support, hand them /llms.txt (the docs index) or /llms-full.txt (the full corpus).
TAP
I sent session_complete but I'm getting status: "ignored".
The most common cause is the 45-second gate. The platform measures
playtime on its own, and if measured play for that day is under 45 seconds,
it holds back the session_complete award. This is a safeguard against abuse
where the event is fired the moment the game loads.
There is nothing to handle on the game side. Awards are idempotent at once per day, so sending the event multiple times is safe, and once the user has actually played 45 seconds or more, the award goes through naturally when the next round ends. Sending it once at the end of every round is the standard pattern.
Other ignored cases: you sent an event name that isn't on the whitelist.
Currently the only award-eligible event is session_complete; other names
are recorded for measurement only.
Can I build a structure that awards more TAP for higher scores?
Not at the moment. Rewards tied to performance (score, wins/losses, ranking) may conflict
with the prize provisions of Korea's Game Industry Promotion Act (게임산업법),
so this is blocked outright at the platform level. Awards are made solely on
the session-level fact that "this player completed a round of this game
today," and scores sent via submitScore() are used for display and rankings
only. See Review Policy §4 for the full background.
What are the TAP rate and the daily cap?
They are server-side policy values, so we intentionally don't fix them in the
documentation (they may be adjusted based on business decisions). Your game
doesn't need to know the values — just handle the points response:
TokenApps.on("points", function (p) {
// p.status: "awarded" (granted; includes p.delta and p.balance) | "duplicate" (today's award already made)
// | "capped" (user's daily cap reached) | "ignored" | "signin_required"
});
capped and duplicate are normal responses too, not errors — passing over
them silently, or showing a light notice, is the appropriate handling.
What is Funding? Can my game run a campaign?
Funding lets users back a game with closed TAP. If a campaign reaches its goal, the reward pool is distributed to backers in contribution order (more, and earlier, ranks higher); if it misses, every backed TAP is refunded in full. There is no cash payment or cash-out anywhere in the flow.
For a game, it is a pre-launch demand test and an early-fan channel. Campaigns are currently opened by the platform on a curated basis — if you would like one for your game, tell us at cs@tokenpost.kr. Self-service campaign creation from the developer console is on the roadmap.
Saves
localStorage doesn't work.
External HTTPS frames retain their origin, but browser privacy policies can block storage. Guard localStorage, cookie and IndexedDB access and keep an in-memory fallback.
TokenApps.save() / load() is the standard storage in this environment.
Data is stored on the user's account, so it carries over when they switch
devices, with one slot per game and up to 64KB serialized. Guests having no
saves is part of the spec — check with canSave() and skip silently.
Examples are in SDK Guide §3.
What happens if a save exceeds 64KB?
It is rejected with 413 too_large and the existing save is kept. A save is
a summary of progress, not a complete log — if you need replays or detailed
records, we recommend keeping them on your own game server and storing only a
lookup key in the save.
Can I use multiple save slots?
Currently it's one slot per game. If you need multiple slots, build the
structure inside the saved value yourself: save({ slots: { a: {...}, b: {...} } }).
The save API's data envelope is deliberately designed with room for this
kind of extension.
Ads · Continue · In-Game Items
requestRewardedAd() keeps returning unavailable.
In most cases this is normal behavior. All of the following situations come
back as the single unavailable response:
| Situation | Notes |
|---|---|
| Guest (signed-out) player | An account is required to grant rewards |
| An ad was already watched within the last 3 minutes | Global minimum interval |
| The user's daily view limit was reached | Caps per user and per (user, game) |
| The user's daily TAP cap was reached | A pre-check that prevents 0 TAP views outright |
| The app is in the Basic stage | Monetization stays closed during the soft launch |
| A placement that isn't allowed | For the games category, currently revive and bonus_points |
There's no need to respond differently per reason — maintaining one path where the game proceeds without the ad covers all of them. This principle is also a review requirement.
The continue sheet vs. calling ads directly — which should I use?
For game-over continues, we recommend requestContinue(). The platform
presents a three-option sheet — "Continue with TAP / Watch an ad to
continue / Give up" — so the player gets a wide set of choices, and your game
code ends with a single result branch. requestRewardedAd() is the
lower-level API for other spots (bonus items, benefits other than revival).
You can use both APIs in the same game.
How do I register items (SKUs)? Can't the game set prices?
Registration is self-service. Open your game's Products panel under
My games and register or edit the SKU (lowercase letters,
digits, -, _), the name (Korean and/or English), the price (in
TAP), and the type (consumable = repeat purchase / durable = owned
once) yourself. Price changes apply to future purchases only, and selling
opens at Full launch — registering ahead of time is fine.
Setting prices from the game side is structurally impossible. No
purchase-related message has an amount field, and the payment sheet and
billing use only the price registered on the server. To display prices in
your game UI, fetch them with getProducts() and render those — they always
match the server.
I want to verify purchases on my game server.
You're right not to trust the client's purchase_result as-is. The
recommended pattern plays the same role as receipt validation on ONE Store or
Google Play:
- The client sends the
tokenit received insessionto your game server. - Your game server calls
GET {api}/api/game/inventorywith that token as Bearer authentication. - Check the owned-items list in the response, then grant with server authority.
The token is valid only for that (user, game) pair, so the authority your game server gains is exactly that much and no more. See Specification §6.8–6.9.
Review · Visibility
My game renders as a white screen inside TokenApps.
Check these two things, in order:
- Frame-blocking headers — If the response carries
X-Frame-Options: SAMEORIGIN/DENYor a CSPframe-ancestorsrestriction, the browser blocks the frame itself. Remove the header or allow tokenapps.io. - Frame-detection code — If the screen is still white with no such
headers, the code often checks something like
window.top !== windowand aborts rendering. Depending on third-party cookies (requiring a login session to enter) produces the same symptom.
The fastest check before submitting is to verify directly at the URL shape
you'll receive after approval, tokenapps.io/app/<slug>/play. If the screen
stays empty for more than 6 seconds, the player automatically shows an "Open
in new tab" prompt, but that is only an emergency escape hatch — embedded
rendering is the review standard.
My game was approved but doesn't appear in home recommendations or categories.
Right after approval, your game is in the Basic stage. It's exposed in the home "New Games" section and via direct links, and play, measurement, and TAP all work. Once play metrics (player count, average session, return rate) accumulate, the operations team promotes it to Full — from then on it's displayed across recommendations and category surfaces, and ad and item monetization opens. The full process is in Review Policy §1.
When does revenue settlement become available?
It opens together with the ad network integration. The settlement structure (revenue event recording → monthly close → payout by bank transfer or USDC) is finalized in design, and terms such as the commission rate and minimum payout will be announced at integration time. If you need to discuss things in advance, contact cs@tokenpost.kr.
How do I check the SDK version?
TokenApps.version returns a string in the form "1.4.0". That said, when you
need to branch, we recommend feature detection over version comparison:
if (typeof TokenApps.requestPurchase === "function") { /* v1.3+ */ }
Every change within v1.x is backward compatible, so code you integrate today will keep working unchanged through future v1 updates. See the release notes for the change history.