# TapFun Integration Guide: Web App

> Integration specification for publishers shipping browser-based web applications on TapFun


## Introduction {#introduction}

This page is the integration specification for publishers and developers who ship a browser-based **web application (Web App)** on TapFun. Your web application runs embedded in an iframe on a TapFun page.

If you ship a Windows exe through cloud gaming, see the [Cloud Gaming specification](../cloudgaming/) instead.

### Setting up communication {#intro-communication}

- Email is the default channel.
- If needed, TapFun creates a Slack Connect channel shared by both companies.
- If you cannot join a Slack Connect channel, we will join your company's communication tool instead.

### Logging in to the Publisher Console {#intro-publisher-console-login}

You can update your exe and change settings in the Publisher Console of the test environment (`https://<TEST_HOST>/publisher`). Your TapFun contact grants login access, so send us the email addresses of the people who will log in.

### Release planning {#intro-release}

Once the remaining issues have a clear path to resolution, we agree on the release date and pricing.

### Reporting issues during development {#intro-issue-report}

When you hit a problem such as "input does not respond" or "unexpected behavior", report it in the communication channel with:

- The app name where the problem reproduces (if the test environment has several copies of the same game, include the app ID or other identifying information)
- The TapFun user account in the test environment where it happened (unless it only happens as a guest)
- Screenshots that show the situation
- A screen recording of the full reproduction steps, if screenshots are not enough
- The steps to reproduce

If the problem comes from intended behavior, both parties may discuss adding or changing a feature.

### Debugging {#intro-debugging}

Add one of these parameters to the game page URL to see the [client API](#client-api) traffic.

| Parameter | Effect |
| --- | --- |
| `?debugLog=1` | Shows a log area over the game screen with the client API requests TapFun received (including [consoleLog](#api-consolelog)). |
| `?verbose=1` | Writes detailed logs to the browser's developer tools console, including the `data` of consoleLog. |

## Environments {#environments}

### Hostnames {#hosts}

| TapFun environment | Hostname | Basic auth on the web UI |
| --- | --- | --- |
| Test | Ask your TapFun contact (written as `<TEST_HOST>` in this document) | Yes (ask your TapFun contact for the user name and password) |
| Production | `https://tapfun.co.jp` | No |

The test environment hostname and its Basic auth user name and password are confidential and are not included in this document. Ask your TapFun contact for them. Replace `<TEST_HOST>` in code examples with the test environment hostname. Do not share them outside your company or anywhere public (public repositories, public channels, and so on).

### Payments in the test environment {#test-payment}

To buy items in the test environment, use one of the following.

- **AmazonPay**: takes you to the "Multi-Payment Service test environment". Select "決済する" (Pay) to complete the payment.
- **Test credit card**: number `4111111111111111`, any future expiry month/year, any 3-digit security code.

### Test play in production {#production-test-play}

To access an app before release, enable maintenance mode in the Publisher Console's basic settings and enter the tester's email address in "Maintenance exception email addresses".

For tests in production that involve purchases, TapFun registers a test-play account for each app. The account receives TapFun credit usable only in that app. Purchases by test-play accounts are cancelled immediately and offset against sales.

## Publisher Console {#publisher-console}

### Basic settings {#pc-basic}

| Field | Description |
| --- | --- |
| App ID | Internal ID of the app. |
| name | Internal name. |
| apiKey | API key for calling the [Web API](#web-api). |
| Bearer token | Value of `Authorization: Bearer` when calling the Web API (format: `<AppID>_<apiKey>`). |
| Maintenance mode | Puts the app into maintenance mode when enabled. |
| Maintenance exception email addresses | Users with these email addresses can play during maintenance. For an unreleased (private) app, enabling maintenance mode also lets these accounts access it. |
| Launch URL | URL that launches your web application. TapFun loads it in an iframe. Placeholders: `{token}` (token for [create_session](#api-create-session)), `{host}` (TapFun hostname, e.g. `tapfun.co.jp`), `{appId}` (app ID). |
| sigKey | HMAC-SHA256 signing key. Used for the `sig` of [purchaseItem](#api-purchaseitem) and for signing the [Callback API](#callback-api). |
| Require sig for purchaseItem | When enabled, purchaseItem without `sig` fails (`invalidSignature`). |
| Frameless display | When enabled, the TapFun header is not shown on the play screen. |
| callback HTTP POST URL | URL notified when an item is purchased ([Callback API](#callback-api)). |
| callback Notice Webhook URL | Webhook URL notified when sending a callback fails and when it recovers (e.g. a Slack incoming webhook). |
| callback Notice Email | Email address notified when sending a callback fails and when it recovers. |
| purchases API compatibility mode | Backward-compatibility mode that returns `userId` in the old format in the [purchases API](#api-purchases). Normally keep it disabled. |


### Details (introduction page) {#pc-detail}

The "詳細情報" (details) tab edits what is shown on the game's introduction page on TapFun.

| Field | Description |
| --- | --- |
| Icon image | The game's icon. Recommended size: 512x512 (square). |
| 2:1 image | A wide image used in listings and on the introduction page. Recommended size: 1920x960. |
| Loading image PC / SP | Images shown while the game starts. Recommended size: 600x520 for PC, 300x260 for smartphones. |
| Controls guide images | Optional images explaining the controls, which players can open from the TapFun screen while playing. |
| Screenshot images | Screenshots shown on the introduction page. |
| Game title | The game name shown on the introduction page and elsewhere. |
| Developer / Publisher | Developer and publisher names shown on the introduction page. |
| Game description | Body text of the introduction page, in [Markdown](https://marked.js.org/#specifications). |
| Third-party EULA | License terms shown on the introduction page, such as third-party licenses included in the game. |

Drag controls guide images and screenshots to reorder them. To delete one, click it to mark it for deletion, then press "追加アップロード & 順序を保存" (upload and save order) to apply the change.

### Launching the game {#pc-launch}

"アプリページ" (app page) at the top of the app screen launches the game with the options below ([Debugging](#intro-debugging)). Unreleased apps show "非公開状態" (private).

| Option | Description |
| --- | --- |
| ログ表示 (show log) | Shows consoleLog API and other logs on screen (same as `?debugLog=1`). |
| verbose | Increases the logs written to the browser console (same as `?verbose=1`). |

### Other tabs {#pc-other-tabs}

| Tab | Contents |
| --- | --- |
| 月別売上 (monthly sales) | Daily purchase counts, sales and cancellations, with a breakdown by sales category (Platform / Publisher). |
| 全ての Purchases / 未完了の Purchases (all / unfinished purchases) | List of purchases. "Unfinished" shows only purchases whose callback has not completed. You can resend the callback from each purchase. In the test environment you can also cancel a purchase and stop callback resends. |
| Callbacks | History of [Callback API](#callback-api) requests. |
| Leaderboards | Leaderboards and their entries. |
| 紹介ページ (introduction page) | Opens the game's introduction page on TapFun in a new tab. |

## Launch and authentication {#webapp}

### How it works {#webapp-overview}

- The TapFun page is the parent frame. Your web application is embedded in an iframe and opened with the [launch URL](#pc-basic) set in the Publisher Console.
- The placeholders in the launch URL (`{token}` / `{host}` / `{appId}`) are replaced with values at launch.
- Communication with the TapFun parent frame uses the [client API](#client-api) (postMessage).

Example launch URL:

```text
https://game.example.com/play?host={host}&token={token}&appId={appId}
```

### User authentication / getting the TapFun user ID {#webapp-auth}

1. Send the value passed in `{token}` from the browser to your game server.
2. Your game server calls [`POST /api/v1/create_session`](#api-create-session), which returns the TapFun user ID.
3. `{token}` is valid for 24 hours in the test environment and 180 seconds in production. It can be reused while valid.

```bash
curl https://<TEST_HOST>/api/v1/create_session -X POST \
  -H "Authorization: Bearer BEARER_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{"token":"TOKEN_HERE"}'
# {"result":"success","userId":"PLYRABCDEFGHIJKLMNOP123456789APPGRP"}
```

Keep the Bearer token on the server. Never send it to the browser.

## Client API {#client-api}

APIs for communication between your web application and TapFun, its parent frame. Messages are exchanged in the player's browser, so **never include secrets.**

| API | Summary |
| --- | --- |
| [purchaseItem](#api-purchaseitem) | Item purchase |
| [getPlayerEnvironment](#api-getplayerenvironment) | Get the player's environment |
| [getOneTimeData](#api-getonetimedata) | Read the URL's `onetime` value once |
| [proposeAuthentication](#api-proposeauthentication) | Prompt the player to log in |
| [openAccount](#api-openaccount) | Open the account page |
| [reboot](#api-reboot) | Restart |
| [consoleLog](#api-consolelog) | Show a log message |

### Transport {#client-api-transport}

| Item | Value |
| --- | --- |
| Sending | `window.parent.postMessage(JSON string, "https://" + {host})` |
| Receiving | The `message` event on `window`. `event.data` is a JSON string. |
| Encoding | UTF-8 |
| Request / response format | JSON |

TapFun accepts a message only if it comes from the embedded iframe and its origin matches the launch URL's origin. On your side, check that `event.origin` is `https://{host}`.

```js
const params = new URLSearchParams(location.search);
const tapfunOrigin = 'https://' + params.get('host'); // include {host} in your launch URL

function send(data) {
  window.parent.postMessage(JSON.stringify(data), tapfunOrigin);
}

window.addEventListener('message', (event) => {
  if (event.origin !== tapfunOrigin) return;
  const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
  console.log('from TapFun', data);
});

send({ req: 'getPlayerEnvironment' });
```

### purchaseItem {#api-purchaseitem}

Shows the item purchase confirmation dialog and processes the payment. When the payment succeeds, TapFun notifies your game server through the [Callback API](#callback-api). Grant the item on your game server when it receives the callback.

| Parameter | Required | Description |
| --- | :-: | --- |
| `req` | ✓ | `purchaseItem` (fixed) |
| `tran` | ✓ | Transaction ID generated by your app, unique per app. Purchase is refused if a past purchase (successful or not) used the same `tran` |
| `code` | ✓ | Product code (any value; used for aggregation on TapFun) |
| `item` | | Composite product code (any value) |
| `amount` | ✓ | Price (300-10000) |
| `name` | ✓ | Product name (any value) |
| `img` | | Product thumbnail image URL |
| `note` | | Free-form field |
| `ret` | | Return target. When charging the TapFun wallet involves a page transition, this sets the path (and after) of the launch URL used to restart the app. It is treated as a launch URL, so placeholders such as `{host}` are needed again. Must start with `/`. Example: `/purchaseReturn?host={host}&token={token}&appId={appId}#anchor` |
| `sig` | * | Signature ([How to sign](#purchaseitem-sig)). Required if "Require sig for purchaseItem" is enabled in the Publisher Console. Always verified when sent |

Request example:

```json
{
  "req": "purchaseItem",
  "tran": "UNIQUE_TRANSACTION_ID_HERE",
  "code": "code_type01",
  "item": "item_type01",
  "img": "https://example.com/img/item.png",
  "amount": 500,
  "name": "300 Gems",
  "ret": "/purchaseReturn?host={host}&token={token}&appId={appId}#anchor",
  "sig": "SIGNATURE_HASH_HERE"
}
```

#### How to sign {#purchaseitem-sig}

Join the following values with `&` in this order, compute HMAC-SHA256 with the Publisher Console's sigKey as the key, and use the lowercase hex string.

```text
tapfunUserId&tran&code&item&amount&name&img&note&ret
```

- `tapfunUserId` is not a request parameter, but it is required in the string to sign.
- Do not omit empty values such as `item` / `img` / `note` / `ret`; keep consecutive `&`. Example: `PLYR123456DBG&ABCD&gem300&&500&300 Gems&&&`
- Compute the signature on your game server. Never send sigKey to the browser.

```js
// Node.js (game server)
import { createHmac } from 'node:crypto';

function purchaseSig(sigKey, p) {
  const s = [p.tapfunUserId, p.tran, p.code, p.item ?? '', p.amount, p.name, p.img ?? '', p.note ?? '', p.ret ?? ''].join('&');
  return createHmac('sha256', sigKey).update(s).digest('hex');
}
```

#### Response notifications {#purchaseitem-response}

Every notification includes `req` (`purchaseItem`), `code` (the requested `code`) and `tran` (the requested `tran`).

| `result` | When | Extra keys |
| --- | --- | --- |
| `received` | The purchase event was received and the confirmation dialog opened | — |
| `purchaseCancel` | The confirmation dialog was closed (purchase cancelled) | — |
| `purchase` | The purchase button in the dialog was pressed | — |
| `purchaseSuccess` | The payment succeeded | — |
| `incompleteCallback` | The purchase failed (sent when the failure modal is shown) | `reason` (below) |
| `parameterError` | Missing parameters, `sig` mismatch, etc. | `reason`: error details (e.g. `required param not found "code"`) |

| `reason` | Description |
| --- | --- |
| `duplicateTran` | The `tran` duplicates a past purchase |
| `unfinishedClallbackPrucahseRemaining` | There is an unfinished callback (e.g. due to a network error). The spelling is exactly as shown |
| `rejected` | The callback response's `result` was `reject` |
| `unknown` | The callback result could not be obtained for an unknown reason |
| `invalidSignature` | The signature did not match |

- If the `tran` matches a past purchase, a pre-purchase check shows an error dialog. If the check is passed due to timing, the error dialog is still shown when the same `tran` is detected after the purchase button is pressed.
- To reproduce the duplicate `tran` error, open TapFun's game page with `?purchasableCheck=0` added to the URL and call purchaseItem from the game with the same `tran`. The pre-purchase check is skipped so the purchase button can be pressed.
- You can see the requests the browser received in the browser's developer tools console.

### getPlayerEnvironment {#api-getplayerenvironment}

Gets the player's environment.

| Parameter | Required | Description |
| --- | :-: | --- |
| `req` | ✓ | `getPlayerEnvironment` (fixed) |

Response:

| Key | Description |
| --- | --- |
| `from` | `getPlayerEnvironment` |
| `userAgent` | The browser's `navigator.userAgent` |
| `isSP` | `1`: smart device (iPhone / iPad / Android) / `0`: other |
| `authenticated` | `1`: logged in / `0`: guest |


### getOneTimeData {#api-getonetimedata}

Reads the `onetime` query string value of the TapFun page URL, once. Useful for deep links. After the request, `onetime` is removed from the URL.

| Parameter | Required | Description |
| --- | :-: | --- |
| `req` | ✓ | `getOneTimeData` (fixed) |

Response:

| Key | Description |
| --- | --- |
| `from` | `getOneTimeData` |
| `onetime` | Decoded value of `onetime`. Empty string if already read or absent |

Example: when the URL is `https://tapfun.co.jp/p/dummy/play?onetime=abcdefg`

```json
// 1st request
{ "from": "getOneTimeData", "onetime": "abcdefg" }
// 2nd request (onetime has been removed from the URL)
{ "from": "getOneTimeData", "onetime": "" }
```

With an encoded value:

```json
// ?onetime=page%3Dqrcode%26code%3Daaaa
{ "from": "getOneTimeData", "onetime": "page=qrcode&code=aaaa" }
// ?onetime=abc%0D%0Adef%0D%0Aghi
{ "from": "getOneTimeData", "onetime": "abc\r\ndef\r\nghi" }
```

### proposeAuthentication {#api-proposeauthentication}

Prompts the player to log in. For a guest, shows the sign-up / login modal.

| Parameter | Required | Description |
| --- | :-: | --- |
| `req` | ✓ | `proposeAuthentication` (fixed) |

Response:

| Key | Description |
| --- | --- |
| `from` | `proposeAuthentication` |
| `authenticated` | `1`: already logged in, nothing was done / `0`: guest, the modal was shown |

### openAccount {#api-openaccount}

Opens the account page. For a guest, shows the sign-up / login modal.

| Parameter | Required | Description |
| --- | :-: | --- |
| `req` | ✓ | `openAccount` (fixed) |

Response: none

### reboot {#api-reboot}

Restarts the game, the same as when the player taps the restart icon in the TapFun UI.

| Parameter | Required | Description |
| --- | :-: | --- |
| `req` | ✓ | `reboot` (fixed) |

Response: none

### consoleLog {#api-consolelog}

Adds a log line to TapFun's log area. It is also written to the browser's developer tools console.

| Parameter | Required | Description |
| --- | :-: | --- |
| `req` | ✓ | `consoleLog` (fixed) |
| `data` | ✓ | A string or JSON string. If `JSON.parse()` succeeds, it is shown in the console as a JSON object. |

Response: none

## Callback API (item purchase notification) {#callback-api}

When a [purchaseItem](#api-purchaseitem) payment succeeds, the TapFun server notifies your game server of the purchase.

### Specification {#callback-spec}

| Item | Value |
| --- | --- |
| Endpoint | "callback HTTP POST URL" in the Publisher Console |
| Source IP address | Test: ask your TapFun contact / Production: `54.178.93.41` |
| Method | HTTP POST |
| Content-Type | `application/json` |
| Encoding | UTF-8 |
| Response | JSON ([Response](#callback-response)) |
| Timeout | 5 seconds |

Request headers:

| Header | Description |
| --- | --- |
| `X-TapFun-Callback-Key` | Unique key per callback |
| `X-TapFun-Retry` | Retry count (only on retries) |
| `X-TapFun-Signature` | HMAC-SHA256 of the request body signed with sigKey (hex). Compute it from the raw body string as received |

```js
// Signature verification example with Node.js (Express)
import { createHmac, timingSafeEqual } from 'node:crypto';

app.post('/tapfun/callback', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = createHmac('sha256', SIG_KEY).update(req.body).digest('hex');
  const actual = req.get('X-TapFun-Signature') || '';
  if (expected.length !== actual.length || !timingSafeEqual(Buffer.from(expected), Buffer.from(actual))) {
    return res.status(400).json({ result: 'error' }); // anything other than success / reject is retried
  }
  const purchase = JSON.parse(req.body);
  // process idempotently by purchase.tran and grant the item
  res.json({ result: 'success' });
});
```

### Notifications and retries {#callback-retry}

- The first failure and the recovery are notified to "callback Notice Email" and "callback Notice Webhook URL" in the Publisher Console.
- At purchase time, after a failure or timeout TapFun waits 0.5 seconds and retries 3 times. If all 3 fail, a failure modal is shown to the player.
- After that, retries continue at these intervals:
  - Up to 10 minutes: every minute
  - Up to 60 minutes: every 10 minutes
  - Up to 24 hours: every hour
  - Up to 14 days: every day
  - After that, you need to re-run it manually from the Publisher Console.
- While a purchase has an unfinished callback, the player cannot buy another product with the same `code`.

### Request {#callback-request}

| Key | Description |
| --- | --- |
| `req` | `purchaseItem` |
| `event` | `purchaseItem` |
| `result` | `success` |
| `key` | Unique key per purchase |
| `appId` | App ID |
| `userId` | TapFun user ID |
| `publisherUserId` | Your user ID for the player, or `null` if not set |
| `fromRegisterDevice` | Device the player signed up to TapFun on (`SP` / `PC`) |
| `purchaseDevice` | Device used for the purchase (`SP` / `PC`) |
| `salesDistinction` | Sales category (`Publisher` / `Platform`) |
| `tran` | Transaction ID |
| `code` | Product code |
| `item` | Composite product code, or `null` |
| `amount` | Price |
| `name` | Product name |
| `img` | Image URL, or `null` |
| `note` | Note, or `null` |
| `createdAt` | Purchase time (UTC) |

```json
{
  "req": "purchaseItem",
  "event": "purchaseItem",
  "result": "success",
  "key": "01HRC7QZV1GVT0WX9G6EKSYCAS",
  "appId": 1,
  "userId": "PLYR01HE2R3JV6Y2NEGRMS4KZQB861DBG",
  "publisherUserId": null,
  "fromRegisterDevice": "PC",
  "purchaseDevice": "PC",
  "salesDistinction": "Publisher",
  "tran": "abcdef123456",
  "code": "gem300",
  "item": "itemGem300",
  "amount": 500,
  "name": "300 Gems",
  "img": "https://example.com/img/item.png",
  "note": null,
  "createdAt": "2024-03-06T06:37:22.000Z"
}
```

### Response {#callback-response}

Your game server must return `success` or `reject` in the JSON `result`. Otherwise TapFun retries. The HTTP status is not evaluated, but 200 is recommended. Redirects are not supported.

```json
// Purchase succeeded
{ "result": "success" }
// Purchase rejected
{ "result": "reject" }
```

- With `success`, the sale is officially recorded.
- With `reject`, the purchase is cancelled and the player's wallet balance is not reduced (the player's history shows the purchase and a refund).
- If your game server detects the same `tran` again, return the result of the existing purchase (idempotency). For example, if the existing purchase with that `tran` succeeded, return `success`.

## Web API {#web-api}

HTTP APIs called from your servers. Keep the Bearer token on the server and never send it to the browser.

### Common specification {#web-api-common}

| Item | Value |
| --- | --- |
| Base URL | `https://<environment hostname>/api/v1` ([Hostnames](#hosts)) |
| Method | HTTP GET / POST |
| Encoding | UTF-8 |
| Request format | URL path, query string, POST data (`application/x-www-form-urlencoded` unless stated otherwise) |
| Response format | JSON. An empty array when an array result has no items. HTTP status is 200 unless stated otherwise |
| Date/time | UTC, `yyyy-mm-ddThh:mm:ss.sssZ` |

### Authentication {#web-api-auth}

Authenticate with the HTTP header `Authorization: Bearer <AppID>_<APIKey>` (the Bearer token in the Publisher Console).

```text
Authorization: Bearer 123_NWU0MjlkY2ViYTVkOTdiYjIwNThmZTkxMDMzYWM4Y2JmNGUyMTQ0Ng
```

On authentication failure the API returns HTTP 403 with:

```json
{"error":"no authorization"}
```

### POST /api/v1/create_session {#api-create-session}

Gets the TapFun user ID from the launch URL's `{token}` ([User authentication](#webapp-auth)). Authentication: required.

| POST data | Required | Description |
| --- | :-: | --- |
| `token` | ✓ | Value passed in the launch URL's `{token}` |

```json
// Success
{ "result": "success", "userId": "<TapFun user ID>" }
// Failure (400): token missing
{ "result": "failure", "message": "token is missing" }
// Failure (400): token invalid
{ "result": "failure", "message": "token is invalid" }
```

### GET /api/v1/purchases {#api-purchases}

Gets the purchase history, newest first. Useful for checking purchases whose callback you missed. Authentication: required.

| Query string | Required | Description |
| --- | :-: | --- |
| `userId` | | TapFun user ID. If omitted, returns purchases for the whole app |
| `key` | | Purchase key |
| `tran` | | Transaction ID |
| `limit` | | Number of results (1-100, default 10). Out of range becomes 10 |
| `nextKey` | | Key for fetching the next results (the `nextKey` of the previous response) |

```json
// Success
{
  "nextKey": "Purchase01HE2R3JV6Y2NEGRMS4KZQB858",   // null when there are no more results
  "result": [
    {
      "key": "Purchase01HE2R3JV6Y2NEGRMS4KZQB861",
      "appId": 1,
      "userId": "PLYR01HE2R3JV6Y2NEGRMS4KZQB861DBG",
      "tran": "abcdef123456",
      "code": "abcdef123456",
      "item": "abcdef123456",
      "amount": 500,
      "name": "300 Gems",
      "img": null,
      "callbackSuccess": false,
      "createdAt": "2024-03-06T06:37:22.000Z"
    }
  ]
}
```

- `callbackSuccess` tells whether the callback succeeded.
- When "purchases API compatibility mode" is enabled in the Publisher Console, the suffix of `userId` is a few characters shorter. Normally keep it disabled.
- There is no failure response (an empty array when nothing matches).

### POST /api/v1/photos {#api-photos}

Saves an image to TapFun photo storage. Players can view and download images saved from games on TapFun. Authentication: required.

| Item | Value |
| --- | --- |
| Content-Type | `multipart/form-data` |
| Query string `userId` | TapFun user ID (required) |
| POST data `file` | PNG / JPEG / WebP image binary (required). The part's `Content-Disposition` must have a `filename` (any value), e.g. `form-data; name="file"; filename="sample.jpg"` |

Limits:

- Max images stored: 1,000 (the oldest are deleted automatically beyond that)
- Max size: 10 MB per image (no overall limit)
- Max width/height: 12,000 pixels
- Max image size: 100 megapixels (e.g. 10,000 × 10,000)
- No wait between calls and no call limit

```bash
curl -F "file=@sample.jpg;type=image/jpeg" \
  -H "Authorization: Bearer 1_8an4dAfsIyEEvBbaKxd55P2Wgf" \
  "https://<TEST_HOST>/api/v1/photos?userId=PLYR01JY39A09EE40YQZRHWTT9EM1VDBG"
```

```json
// Success
{ "result": "success" }
// Failure (404): user ID not found
{ "result": "failure", "message": "userId is not found" }
// Failure (400): other (file missing, too large, invalid type, etc.)
{ "result": "failure", "message": "other problems." }
```

### GET /api/v1/leaderboard/entries {#api-leaderboard-get}

Gets leaderboard entries. Authentication: required.

| Query string | Required | Description |
| --- | :-: | --- |
| `name` | ✓ | Leaderboard name |
| `sort` | | `asc`: ascending / `desc`: descending (default) |
| `userId` | | User ID |
| `arround` | | Gets entries around the given user (1-100). Works when `userId` is set |
| `offset` | | Start position (0-9999). Out of range becomes 0 |
| `limit` | | Number of entries (1-1000, default 100). Out of range becomes 100 |

```json
// Success
{
  "result": "success",
  "entries": [
    {
      "position": 1,
      "userId": "PLYR01HE2R3JV6Y2NEGRMS4KZQB861DBG",
      "userName": "Player",               // UTF-8, no emoji, 1-12 characters
      "score": 12345,
      "note": "extra info",               // up to 8192 characters
      "createdAt": "2025-09-24T00:00:00.000Z",
      "updatedAt": "2025-09-24T00:00:00.000Z"
    }
  ]
}
// Failure (404): user ID not found
{ "result": "failure", "message": "userId is not found" }
// Failure (400): other
{ "result": "failure", "message": "other problems." }
```

### POST /api/v1/leaderboard/entries {#api-leaderboard-post}

Creates or updates a leaderboard entry. If the score is less than or equal to the existing score, it is not updated. A leaderboard is created automatically for each `name`. Authentication: required.

| POST data | Required | Description |
| --- | :-: | --- |
| `name` | ✓ | Leaderboard name. 1-64 characters of letters, digits, `_` and `-` (`/^[0-9a-zA-Z_-]{1,64}$/`) |
| `userId` | ✓ | User ID |
| `score` | ✓ | Score (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807) |
| `note` | | Extra info (UTF-8, 0-8192 characters) |
| `force` | | Pass `1` to save even if the score is lower than the existing one |

```json
// Success
{ "result": "success" }
// Failure (404): user ID not found
{ "result": "failure", "message": "userId is not found" }
// Failure (400): other
{ "result": "failure", "message": "other problems." }
```

### POST /api/v1/leaderboard/entries/delete {#api-leaderboard-delete}

Deletes a leaderboard entry. Returns success even if there is no entry. Authentication: required.

| POST data | Required | Description |
| --- | :-: | --- |
| `name` | ✓ | Leaderboard name (`/^[0-9a-zA-Z_-]{1,64}$/`) |
| `userId` | ✓ | User ID |

```json
// Success
{ "result": "success" }
// Failure (404): user ID not found
{ "result": "failure", "message": "userId is not found" }
// Failure (400): other
{ "result": "failure", "message": "other problems." }
```

## Glossary {#glossary}

### TapFun user ID {#glossary-user-id}

- A unique ID of up to 64 characters that identifies a user. Issued per app.
- Characters: `0-9`, `a-z`, `A-Z`.
- Format: `PLYR` + ULID + a per-app suffix of 3-7 characters (e.g. `DBG`, `XENO`, `MOCODVLP`).

### App ID {#glossary-app-id}

An unsigned int ID that identifies an app.

### API key {#glossary-api-key}

A 26-character string issued per app. Characters: `0-9`, `A-Z`.


### Publisher Console {#glossary-publisher-console}

The management console for publishers, used for app settings, the introduction page, sales and more. The test and production environments each have their own, and TapFun registers who can log in.

### Introduction page {#glossary-introduction-page}

The game's page on TapFun (`https://tapfun.co.jp/p/<app>`). The play screen is `https://tapfun.co.jp/p/<app>/play`. Edit its contents under [Details](#pc-detail) in the Publisher Console.

### Guest {#glossary-guest}

A player who is not logged in to TapFun.

### TapFun wallet {#glossary-wallet}

The balance a player charges on TapFun, used for purchases on TapFun.

### TapFun credit {#glossary-credit}

Wallet balance given to test-play accounts in production, usable only in the target app ([Test play in production](#production-test-play)).

### Product code / composite product code {#glossary-item-code}

- **Product code (`code`)**: identifies the kind of product and is used for aggregation on TapFun. While a purchase has an unfinished callback, the player cannot buy another product with the same product code.
- **Composite product code (`item`)**: an auxiliary code your app can use freely. TapFun does not interpret it and returns it as-is in the Callback API and the purchases API.

## Changelog {#changelog}

| Date | Change |
| --- | --- |
| 2026-10-02 | Published this document. |
