Skip to content

Repository files navigation

BattleTech Data API

A GraphQL API serving BattleTech unit, equipment, faction, and era data sourced from MegaMek and the Master Unit List (MUL).

Stack

  • API: Rust · axum 0.8 · async-graphql 7 · sqlx 0.8 · PostgreSQL 16
  • Scraper: imports from MegaMek unit files (MTF + BLK formats) including per-slot critical hit tables, the Master Unit List (BV, roles, availability, clan names), equipment stats seed data (including ammo shots-per-ton), and ammo-to-weapon linkage
  • Ops: Prometheus metrics at /metrics, Dockerfile (musl/Alpine), IP rate limiting

Quick start

1. Start Postgres

docker run -d --name bt-postgres \
-e POSTGRES_PASSWORD=pass \
-p 5432:5432 \
postgres:16

2. Configure environment

cp .env.example .env
# Edit DATABASE_URL if needed

3. Install tooling and run migrations

cargo install sqlx-cli --no-default-features --features postgres
sqlx migrate run

4. Seed the database

Option A — Use the included seed dump (fastest):

./seed/load.sh

This loads a pre-exported snapshot with all MegaMek + MUL data in seconds.

Option B — Import from source:

Download a MegaMek release tarball (e.g. MegaMek-0.50.11.tar.gz) and extract it. The unit data is at data/mekfiles/unit_files.zip inside the extracted directory.

# Step 1: Import MegaMek unit files
cargo run -p scraper@0.1.0 --release -- megamek \
--zip /path/to/MegaMek-0.50.11/data/mekfiles/unit_files.zip \
--version "0.50.11"# Step 2 (optional): Enrich with MUL data (BV, cost, role, availability, clan names)# First fetch MUL data to local files:
cargo run -p scraper@0.1.0 --release -- mul-fetch \
--output-dir ./mul-data --delay-ms 1000
# Then import into DB:
cargo run -p scraper@0.1.0 --release -- mul-import \
--data-dir ./mul-data

5. Run the API

cargo run -p api

The server starts on http://localhost:8080. In debug builds, GraphiQL is available at GET /graphql.

Endpoints

EndpointDescription
POST /graphqlGraphQL API
GET /graphqlGraphiQL playground (debug builds only)
GET /healthLiveness check — always 200
GET /readyReadiness check — verifies DB connectivity and schema version
GET /metricsPrometheus metrics
GET /schema.graphqlFull GraphQL schema in SDL format
GET /llms.txtLLM-optimized API reference document

GraphQL API

Example queries

# Paginated unit search with filters
{
units(first: 20, nameSearch: "Atlas", techBase: "inner_sphere") {
edges {
node {
slugfullNameclanNametonnagebvcostintroYearrulesLevelrole
}
}
pageInfo {
totalCounthasNextPageendCursor
}
}
}
# Single unit with full detail and resolved component types
{
unit(slug: "atlas-as7-d") {
fullNameclanNametonnagetechBasebvcostrolemechData {
configisOmnimechengineRatingwalkMprunMpjumpMpheatSinkCount # Resolved component types with construction propertiesengine { nameweightMultiplierctCritsstCrits }
armor { namepointsPerToncrits }
structure { nameweightFractioncrits }
heatsink { namedissipationcritsweight }
gyro { nameweightMultipliercrits }
cockpit { nameweightcrits }
myomer { name }
 # Raw MegaMek strings (always available as fallback)engineTypeRawarmorTypeRaw
}
loadout {
equipmentNamelocationquantityisRearFacing
}
locations {
locationarmorPointsrearArmorstructurePoints
}
quirks {
nameisPositivedescription
}
availability {
factionSlugfactionNameeraSlugeraNameavailabilityCode
}
}
}
# Search Clan units by alternate name# nameSearch matches both fullName and clanName
{
units(first: 5, nameSearch: "Fire Moth") {
edges {
node { slugfullNameclanName }
}
}
}
# Filter by faction, era, and role
{
units(first: 20, factionSlug: "clan-wolf", eraSlug: "clan-invasion", role: "Striker") {
edges {
node {
slugfullNametonnagebvrole
}
}
pageInfo { totalCount }
}
}
# All Clan factions
{
allFactions(isClan: true) {
slugnameshortName
}
}
# Chassis with variants filtered by rules level
{
chassis(slug: "atlas-mech") {
nameunitTypetonnagevariants(rulesLevel: STANDARD) {
slugfullNamerulesLevelbvintroYear
}
}
}
# List chassis filtered by rules level (cumulative)
{
allChassis(unitType: MECH, rulesLevel: STANDARD) {
slugnametechBasetonnage
}
}
# Equipment with stats and ammo relationships
{
equipment(slug: "autocannon-10") {
nametonnagecritsdamageheatrangeShortrangeMediumrangeLongbvobservedLocationsammoTypes { slugname }
}
}
# Equipment search with builder filters
{
allEquipment(maxTonnage: 2.0, maxCrits: 3, observedLocation: "right_arm") {
edges {
node { slugnametonnagecrits }
}
}
}
# Construction reference — all component types in one request
{
constructionReference {
engineTypes { slugnametechBaseweightMultiplierctCritsstCrits }
armorTypes { slugnamepointsPerToncrits }
structureTypes { slugnameweightFractioncrits }
heatsinkTypes { slugnamedissipationcritsweight }
gyroTypes { slugnameweightMultipliercrits }
cockpitTypes { slugnameweightcrits }
myomerTypes { slugname }
engineWeights { ratingstandardWeight }
internalStructure { tonnageheadcenterTorsosideTorsoarmleg }
}
}

Filters

The units query supports the following filters:

FilterTypeDescription
nameSearchStringCase-insensitive substring match on fullName and clanName
techBaseStringinner_sphere, clan, mixed, primitive
rulesLevelEnumCumulative rules level filter. E.g. ADVANCED includes introductory, standard, and advanced
tonnageMin / tonnageMaxFloatWeight range in metric tons
factionSlugStringUnits available to this faction (e.g. "clan-wolf")
eraSlugStringUnits available in this era (e.g. "clan-invasion")
isOmnimechBoolOmniMechs only (true) or non-OmniMechs (false)
configStringChassis config: Biped, Quad, Tripod, LAM
engineTypeStringEngine type (e.g. "XL Engine", "Fusion Engine")
hasJumpBoolJump-capable mechs only
roleStringTactical role (e.g. "Juggernaut", "Sniper", "Striker")

The allEquipment query supports additional builder-oriented filters:

FilterTypeDescription
nameSearchStringCase-insensitive substring match on equipment name
categoryStringEquipment category in snake_case (e.g. "energy_weapon")
techBaseStringinner_sphere, clan, mixed, primitive
rulesLevelEnumCumulative rules level filter. E.g. ADVANCED includes introductory, standard, and advanced
maxTonnageFloatEquipment weighing at most this many tons
maxCritsIntEquipment consuming at most this many critical slots
observedLocationStringEquipment observed at this location (e.g. "right_arm")
ammoForSlugIDAmmo types compatible with this weapon slug

Limits

  • Query depth: 20
  • Query complexity: 500
  • unitsByIds: max 24 slugs per call
  • Pagination: max 100 per page
  • Rate limit: 100 req burst / ~120 req/min sustained per IP

Docker

docker build -t battletech-api .
docker run -p 8080:8080 \
-e DATABASE_URL=postgres://postgres:pass@host.docker.internal:5432/battletech \
-e ALLOWED_ORIGINS=https://yourdomain.com \
-e EXPECTED_SCHEMA_VERSION=1 \
battletech-api

The image is a statically-linked musl binary on Alpine (~10 MB).

Environment variables

VariableDefaultDescription
DATABASE_URLPostgreSQL connection string
PORT8080Listen port
ALLOWED_ORIGINSComma-separated CORS origins; use * to allow all
EXPECTED_SCHEMA_VERSION1Schema version checked by /ready
RUST_LOGinfoLog filter (e.g. debug, warn, api=debug)
PUBLIC_BASE_URLhttp://localhost:{PORT}Base URL used in /llms.txt and /schema.graphql references

Deployment (timeweb.cloud)

Infrastructure is managed with Terraform in the infra/ directory. It provisions a managed Kubernetes cluster and a managed PostgreSQL instance on timeweb.cloud, connected via a private VPC.

1. Configure variables

cd infra
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars — set twc_token, db_password, and other values

2. Provision infrastructure

terraform init
terraform plan
terraform apply

3. Upload static assets to S3

The S3 bucket (resources.battledroids.ru) hosts static assets such as record sheet images. After terraform apply creates the bucket:

cd infra
./scripts/upload-assets.sh ../../battletech-roster-builder/packages/record-sheet/assets

This syncs template and pattern images under roster/templates/ and roster/patterns/, and configures CORS for roster.battledroids.ru.

4. Get kubeconfig and apply K8s manifests

# Save kubeconfig from Terraform output
terraform output -raw kubeconfig >~/.kube/battletech.yaml
export KUBECONFIG=~/.kube/battletech.yaml
# Create namespace and deploy the API
kubectl apply -f k8s/namespace.yaml
kubectl create secret generic battletech-api-secrets \
--namespace=battletech \
--from-literal=DATABASE_URL='postgres://USER:PASS@DB_HOST:5432/battletech'
kubectl apply -f k8s/api.yaml

5. Run migrations and seed the database

Connect to the managed PostgreSQL using the VPC-internal address (from terraform output db_host), then:

DATABASE_URL='postgres://USER:PASS@DB_HOST:5432/battletech' sqlx migrate run
DATABASE_URL='postgres://USER:PASS@DB_HOST:5432/battletech' ./seed/load.sh

Data sources

MegaMek

Units, chassis, equipment, locations, loadout, quirks, and mech-specific data are imported from MegaMek release files. The scraper reads .mtf (mech) and .blk (vehicle, aerospace, etc.) formats from MegaMek's unit_files.zip.

Master Unit List (MUL)

The scraper enriches MegaMek data with information from the official Master Unit List:

  • Battle Value (BV) and C-bill cost for game balancing
  • Tactical roles (Juggernaut, Sniper, Striker, Brawler, etc.)
  • MUL ID linking to the official entry
  • Clan names — alternate IS/Clan reporting names for dual-name OmniMechs (e.g. "Fire Moth" for "Dasher")
  • Faction/era availability — which factions field each unit in which eras

MUL data is fetched via mul-fetch (saves to local files, resume-safe) and imported via mul-import. A pre-fetched archive is included at mul-data.zip. Units are matched by slug (~95% match rate for BattleMechs/vehicles).

Data overview

All imports are idempotent — inserts use ON CONFLICT ... DO UPDATE.

TableRowsSource
unit_chassis~1,670MegaMek
units~6,535MegaMek
unit_mech_data~4,225MegaMek
equipment~2,875MegaMek
unit_loadout~70,550MegaMek
unit_locations~33,150MegaMek
unit_availability~100,000+MUL
eras10seed + MUL
factions~70seed + MUL
engine_types9construction ref
armor_types9construction ref
structure_types6construction ref
heatsink_types4construction ref
gyro_types5construction ref
cockpit_types6construction ref
myomer_types4construction ref
engine_weight_table79construction ref
mech_internal_structure17construction ref

About

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages