Third-Party Interface

Build a tool that BakoshApp can drive: serve four HTTP endpoints on a local port, and your program reacts to TikTok and Kick gifts, likes, follows and chat.

BakoshApp can run actions inside your program. Your tool serves a small HTTP API on a local port; BakoshApp reads your list of actions and calls one whenever a TikTok or Kick event matches the user's trigger. Anything you can control from code — smart lights, a microcontroller, a soundboard, another game, your own bot — can react to a live stream without a BakoshApp mod.

How it fits together

  1. Your tool listens on http://127.0.0.1:8833.
  2. The user connects it in Settings → Third-Party (host and port) and checks it with Test connection.
  3. On a mod's Actions tab, the user clicks Add new, picks a trigger, and in All Actions → External configures a Third-Party Action: BakoshApp fills two lists with your categories and actions.
  4. On every matching event, BakoshApp calls your tool, and your code does the work.

The connection is one-way: BakoshApp calls you, never the other way round.

Port and address

  • 8833 is the default. The user can change it in Settings → Third-Party; tell your users which port to enter if you use another one.
  • 8832 can't be used: BakoshApp itself listens there (that's how TikFinity finds BakoshApp), and the setting refuses it.
  • There is no authentication. Listen on 127.0.0.1, not 0.0.0.0, so the API isn't reachable from the network.
  • BakoshApp always uses plain HTTP.

Endpoints

Your tool must serve all four. Every response is JSON with a data member.

1. App info

Identifies your tool. BakoshApp shows it as "Connected with Mock Tool 1.0 by Bakosh" in Settings and in the action configurator.

GET /api/app/info
{ "data": { "name": "Mock Tool", "version": "1.0", "author": "Bakosh" } }

2. Categories

The first list in the configurator.

GET /api/features/categories
{
  "data": [
    { "categoryId": "smart_device_control", "categoryName": "Control Smart Devices" },
    { "categoryId": "serial_port_control",  "categoryName": "Control Serial Port" }
  ]
}

3. Actions

The second list: the actions of one category. categoryId is URL-encoded.

GET /api/features/actions?categoryId=smart_device_control
{
  "data": [
    { "actionId": "turn_lights_on",  "actionName": "Turn Lights On" },
    { "actionId": "turn_lights_off", "actionName": "Turn Lights Off" }
  ]
}

The lists show your items in the order you return them. An action the user has already saved but you no longer return stays at the top of the list, so it doesn't silently change.

Warning

categoryId and actionId are all BakoshApp stores for a configured action. Keep them stable: renaming an id breaks existing setups. Names are for display only and can change any time.

4. Execute

Called every time a matching event runs the action.

POST /api/features/actions/exec
{
  "categoryId": "smart_device_control",
  "actionId": "turn_lights_on",
  "context": {
    "triggerTypeId": 4,
    "eventType": "gift",
    "source": "eulerstream",
    "platform": "tiktok",
    "userId": "123456789",
    "username": "testuser",
    "nickname": "Test User",
    "giftName": "Rose",
    "giftId": 5655,
    "repeatCount": 3,
    "diamondCount": 1,
    "coins": 1,
    "profilePictureUrl": "https://…"
  }
}

Answer with an empty data array once you've accepted the request:

{ "data": [] }

Timing

  • BakoshApp waits up to 5 seconds for the three GET endpoints and 10 seconds for exec. A request that fails or times out isn't repeated.
  • If the action has Counts above 1, BakoshApp calls exec that many times, one after another: it waits for your answer, then for the row's Interval, then calls again.
  • Calls for the same row come one at a time; different rows can call you in parallel.

So answer quickly and do slow work in the background — viewers can send gifts faster than a serial port moves.

The context object

context describes the event that ran the action. Only fields that exist for that event are present — check before you read.

FieldTypePresent
triggerTypeIdnumberalways — see below
eventTypestringalways — gift, like, chat, follow, share, joined, subscribe, tikfinity or test
sourcestringalways — eulerstream (TikTok), kick, tikfinity or test
platformstringTikTok and Kick events — tiktok or kick
usernamestringalways — the viewer's handle
nicknamestringTikTok and Kick events — the display name
userIdstringTikTok and Kick events
profilePictureUrlstringusually (TikTok avatar links expire)
commentstringchat messages; on Kick also the message attached to a gift
giftNamestringgifts
giftIdnumberTikTok gifts
repeatCountnumberTikTok gifts — gifts in the streak; Kick gifted subscriptions — number of recipients
diamondCountnumbergifts — value in Diamonds (TikTok) or price in Kicks (Kick)
coinsnumbersame as diamondCount
likeCountnumberlikes
subMonthnumbersubscriptions

Events relayed from TikFinity carry only triggerTypeId (always 0), source, eventType and username.

Trigger types

triggerTypeId is the type of the trigger the user configured, so a gift chosen by name and a gift matched by value are told apart:

IdTrigger
1Share
3Min-Max (gift value range)
4Gift
6Joined
7Like
9Follow
10Subscribe / Super Fan
11Chat
0TikFinity, or anything else

Treat unknown ids as 0; more may be added.

The Test button

Test on the action row (and its hotkey) calls exec with sample data: source and eventType are test, triggerTypeId comes from the row's trigger, the viewer is bakosh_app, and the gift is a Rose (giftId 5655). There is no platform.

Reporting problems

Return a message to describe what went wrong:

{ "message": "Serial port COM3 is not available" }
  • On the GET endpoints, any message makes BakoshApp treat the answer as an error, even with HTTP 200.
  • On exec, the message is written to the BakoshApp console and log file as a warning. The user doesn't get a pop-up.

HTTP error statuses are respected too; without a message, the log shows only the status code.

Working with TikFinity too

The interface matches TikFinity's third-party interface — the same four paths and field names — so one tool can serve both. The ports differ: TikFinity looks for tools on 8832, which BakoshApp occupies while it runs, so give BakoshApp another port.

TikFinity is a web app and needs CORS headers (Access-Control-Allow-Origin: * and friends) plus answers to OPTIONS preflight requests. BakoshApp doesn't need them, but sending them does no harm. BakoshApp sends more context fields than TikFinity — ignore the ones you don't use.

A complete working tool

No dependencies — save as tool.mjs and run node tool.mjs:

import http from 'node:http';
 
const PORT = 8833;
 
const APP_INFO = { name: 'Mock Tool', version: '1.0', author: 'Bakosh' };
 
const CATALOG = [
  {
    categoryId: 'smart_device_control',
    categoryName: 'Control Smart Devices',
    actions: [
      { actionId: 'turn_lights_on', actionName: 'Turn Lights On' },
      { actionId: 'turn_lights_off', actionName: 'Turn Lights Off' },
    ],
  },
];
 
// Wildcard CORS keeps the same tool usable from TikFinity as well.
const CORS = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': '*',
  'Access-Control-Allow-Methods': '*',
};
 
const send = (res, status, body) => {
  res.writeHead(status, { 'Content-Type': 'application/json', ...CORS });
  res.end(JSON.stringify(body));
};
 
const readBody = (req) =>
  new Promise((resolve) => {
    let raw = '';
    req.on('data', (c) => { raw += c; });
    req.on('end', () => { try { resolve(JSON.parse(raw || '{}')); } catch { resolve({}); } });
  });
 
// Your actual work goes here. Return a string to report a problem.
function runAction(categoryId, actionId, context) {
  console.log(`${categoryId}/${actionId} fired by ${context.username ?? 'someone'} on ${context.platform ?? context.source}`);
  // turnLightsOn() …
  return null;
}
 
http.createServer(async (req, res) => {
  const url = new URL(req.url, `http://127.0.0.1:${PORT}`);
 
  if (req.method === 'OPTIONS') {           // preflight (TikFinity only)
    res.writeHead(204, CORS);
    return res.end();
  }
 
  if (req.method === 'GET' && url.pathname === '/api/app/info') {
    return send(res, 200, { data: APP_INFO });
  }
 
  if (req.method === 'GET' && url.pathname === '/api/features/categories') {
    return send(res, 200, {
      data: CATALOG.map(({ categoryId, categoryName }) => ({ categoryId, categoryName })),
    });
  }
 
  if (req.method === 'GET' && url.pathname === '/api/features/actions') {
    const cat = CATALOG.find((c) => c.categoryId === url.searchParams.get('categoryId'));
    return send(res, 200, { data: cat ? cat.actions : [] });
  }
 
  if (req.method === 'POST' && url.pathname === '/api/features/actions/exec') {
    const body = await readBody(req);
    const problem = runAction(body.categoryId ?? '', body.actionId ?? '', body.context ?? {});
    return problem ? send(res, 200, { message: problem }) : send(res, 200, { data: [] });
  }
 
  send(res, 404, { message: `No such endpoint: ${url.pathname}` });
}).listen(PORT, '127.0.0.1', () => console.log(`Listening on http://127.0.0.1:${PORT}`));

Testing your tool

  1. Start your tool.
  2. In BakoshApp, open Settings → Third-Party, check host and port, and click Test connection. You should see your tool's name, version and author.
  3. On a mod's Actions tab, click Add new, pick a trigger and click Next. In All Actions → External, click Configure next to Third-Party Action, choose your category and action, and save.
  4. Click Test on the new row — your tool receives an exec with eventType: "test".
  5. Go live and check that real events arrive.

If the configurator says "Unable to connect! Check your integration.", check that the tool listens on 127.0.0.1, that the port matches Settings, and that GET /api/app/info returns its answer inside data.