Scripts API
Overview
The Scripts API lets you browse, create, update, delete, download, and favourite BattleScript scripts. Scripts use the field name for their title and a visibility field set to either "public" or "private". The owning user is referenced by ownerId.
Responses are returned as raw JSON objects — there is no { success, data } envelope — except where explicitly noted below (delete, download count, and favourite).
A game is a script that holds two sources: the game script in content and its settings script in configContent. The game creator saves both. A game has kind set to "game" and downloads as one .bcgame bundle that a tagger installs.
Authentication Note
Endpoints marked as requiring a session are authenticated by the NextAuth session cookie described in the Authentication API. Mutating an existing script (update, delete) additionally requires that the authenticated user is the script's owner. Listing and reading public scripts needs no authentication.
Endpoints
/api/scripts
Description: Returns a paginated list of scripts with optional filtering and sorting. Authentication is optional.
Query Parameters:
search— free-text search string.tags— tag to filter by; repeatable to filter on multiple tags.sort—latest(default) orpopular.page— page number (defaults to1).limit— results per page (defaults to10).favourites— set totrueto limit to the current user's favourited scripts.authorid— filter to scripts owned by a specific user id. Use this to list a user's scripts.
Response:
{
"results": [ /* array of script objects */ ],
"pagination": {
"total": 0,
"page": 1,
"limit": 10,
"pages": 0
},
"filters": {
"tags": [ "string" ]
}
}Example:
// Search popular scripts tagged "fps" and "team"
fetch('/api/scripts?search=arena&tags=fps&tags=team&sort=popular&page=1&limit=10')
.then(response => response.json())
.then(data => console.log(data.results, data.pagination))
.catch(error => console.error('Error:', error));/api/scripts
Description: Creates a new script owned by the authenticated user. Requires a session.
Request Body:
{
"name": "string",
"description": "string",
"content": "string",
"configContent": "string", // optional: a settings script makes it a game
"tags": [ "string" ], // optional
"visibility": "public", // optional: "public" | "private"
"notes": "string" // optional
}Response: the created script object.
Example:
fetch('/api/scripts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
name: 'My First Game',
description: 'A simple deathmatch mode',
content: 'program { type "game"; name "My First Game"; }',
tags: ['deathmatch'],
visibility: 'public'
})
})
.then(response => response.json())
.then(script => console.log('Created', script))
.catch(error => console.error('Error:', error));/api/scripts/{id}
Description: Retrieves a single script by its id. No authentication required.
Path Parameters:
id— the script id (a MongoDB ObjectId).
Response: the script object, or a 404 if not found.
Example:
const id = '64f0c0ffee0000000000abcd';
fetch(`/api/scripts/${id}`)
.then(response => response.json())
.then(script => console.log(script))
.catch(error => console.error('Error:', error));/api/scripts/{id}
Description:Updates an existing script. Requires a session, and the authenticated user must be the script's owner.
Request Body: (all fields optional)
{
"name": "string",
"description": "string",
"content": "string", // a new version
"configContent": "string", // the new version's settings script; left out, the
// previous version's is kept; "" removes it
"visibility": "private", // "public" | "private"
"tags": [ "string" ],
"notes": "string"
}Response: the updated script object.
Example:
const id = '64f0c0ffee0000000000abcd';
fetch(`/api/scripts/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
description: 'Updated description',
visibility: 'private'
})
})
.then(response => response.json())
.then(script => console.log('Updated', script))
.catch(error => console.error('Error:', error));/api/scripts/{id}
Description:Deletes a script. Requires a session, and the authenticated user must be the script's owner.
Response:
{
"success": true
}Example:
const id = '64f0c0ffee0000000000abcd';
fetch(`/api/scripts/${id}`, {
method: 'DELETE',
credentials: 'include'
})
.then(response => response.json())
.then(data => console.log(data.success))
.catch(error => console.error('Error:', error));/api/scripts/{id}/download
Description:Compiles the script's current version and returns the binary a tagger loads (a .bin file) as application/octet-stream. It compiles the way the firmware compiles its own scripts, with include "./battlecore/…"resolved from the site's copy of the firmware's declarations. Public scripts are available to anyone; a private script only to its owner. It does not count as a download: that is the POST below.
Response: the binary, with a Content-Disposition: attachment header. 404for a missing script or someone else's private one; 422 with { "error": "…" }holding the compiler's message when the script does not compile.
Example:
const id = '64f0c0ffee0000000000abcd';
fetch(`/api/scripts/${id}/download`, { credentials: 'include' })
.then(response => response.blob())
.then(blob => {
const url = URL.createObjectURL(blob);
// ...trigger a download from the object URL
})
.catch(error => console.error('Error:', error));/api/scripts/{id}/bcgame
Description: Builds a game as one .bcgame bundle: both its scripts compiled the same way as the download above, packed with an info.cborthat gives the game's id (its name as a slug of a-z 0-9 -, at most 23 characters), name, version, author, the date the version was saved and the compiler version. Public games are available to anyone; a private one only to its owner.
Response: the bundle as application/octet-stream, named <id>.bcgame. 404for a missing script, someone else's private one, or a script that is not a game; 422 with { "error": "…", "part": "game.bst" } when one of its scripts does not compile (part says which, or "bundle" when the game breaks a bundle limit).
/api/scripts/{id}/download
Description:Increments the script's download counter. Public scripts can be counted by anyone; private scripts only by their owner.
Response:
{
"success": true,
"downloads": 0,
"message": "string"
}Example:
const id = '64f0c0ffee0000000000abcd';
fetch(`/api/scripts/${id}/download`, {
method: 'POST',
credentials: 'include'
})
.then(response => response.json())
.then(data => console.log('Total downloads:', data.downloads))
.catch(error => console.error('Error:', error));/api/scripts/{id}/favourite
Description: Toggles whether the authenticated user has favourited the script. Requires a session.
Response:
{
"success": true,
"isFavourite": true,
"likes": 0
}Example:
const id = '64f0c0ffee0000000000abcd';
fetch(`/api/scripts/${id}/favourite`, {
method: 'POST',
credentials: 'include'
})
.then(response => response.json())
.then(data => console.log('Favourited:', data.isFavourite, 'Likes:', data.likes))
.catch(error => console.error('Error:', error));Public Games API
For the BattleCore phone app, which installs games on taggers. These two endpoints need no sign-in and only ever see public games.
/api/public/games
Description: The public games, newest change first. No sign-in: this is the list the phone app shows. Only games whose visibility is "public" are listed.
Query Parameters:
search— plain text, matched against the name, description and tags.page— page number (defaults to1).limit— results per page, 1 to 50 (defaults to20).
Response:
{
"results": [
{
"id": "64f0c0ffee0000000000abcd", // for the download below
"gameId": "capture-the-flag", // the id the tagger knows it by
"name": "Capture the Flag",
"description": "string",
"author": "string",
"tags": [ "string" ],
"version": 3,
"updatedAt": "2026-10-01T09:30:00.000Z",
"likes": 0,
"downloads": 0
}
],
"pagination": { "total": 1, "page": 1, "limit": 20, "pages": 1 }
}Example:
fetch('https://battlescript.io/api/public/games?search=flag&page=1&limit=20')
.then(response => response.json())
.then(data => console.log(data.results, data.pagination))
.catch(error => console.error('Error:', error));/api/public/games/{id}/bcgame
Description: One public game as a .bcgame bundle, built as described for GET /api/scripts/{id}/bcgame above. No sign-in. A private or unlisted game is not available here.
Response: the bundle as application/octet-stream, named <gameId>.bcgame. 400 for an id that is not a valid id; 404 when there is no public game with that id; 422 with { "error": "…", "part": "config.bst" }holding the compiler's message when the game does not compile.
The bundle is the .bcgame format, version 1: the ASCII BCGM, a format byte, a table of files sorted by name, the files' bytes, and a SHA-256 of everything before it. It holds config.bin, game.bin and info.cbor. The first 8 bytes of the SHA-256 are the game's identity on the radio. One version always downloads as the same bytes.
Example:
const id = '64f0c0ffee0000000000abcd';
fetch(`https://battlescript.io/api/public/games/${id}/bcgame`)
.then(response => {
if (!response.ok) return response.json().then(body => { throw new Error(body.error); });
return response.arrayBuffer();
})
.then(bytes => console.log('Bundle of', bytes.byteLength, 'bytes'))
.catch(error => console.error('Error:', error));Listing a User's Scripts
There is no dedicated per-user scripts endpoint. To fetch the scripts owned by a particular user, call GET /api/scripts with the authorid query parameter:
const userId = '64f0c0ffee0000000000abcd';
fetch(`/api/scripts?authorid=${userId}`)
.then(response => response.json())
.then(data => console.log(data.results))
.catch(error => console.error('Error:', error));