HTTP Beacons API

The Beacons HTTP API allows an external user with a blessed key to query the world servers directly to query details of all beacons on the world and a “beacon map” of the world returning GZIP compressed little-endian binary packed data. Each world has a unique base API URL that can be requested from the universe discovery server. The API URLs will rarely change and are safe to cache for 24 hours. The HTTP Beacons route should be appended to the world API URLs to request the map per world.

How to query the per world base API URLs

How to query the HTTP Beacons API

  • GET request route is: /api/beacons

  • The request should include a header with your API key: Boundless-API-Key. There are different keys for the Testing and Live servers.

  • The response is streamed back with Chunked Transfer Encoding returning a GZIP encoded binary data pack (gunzip on Linux/macOS or some other utility on Windows OS).

  • The response will run so as not to stall the server in any way, but will generally take less than 1 second to process.

  • The returned beacon details may not be fully consistent if changes occur during processing but the data will be fully self consistent; it will not reference beacons whose details are not listed in the response.

Error Codes

  • a 403 status will be returned if the API key was not acceptable.

  • a 503 status will be returned if the server is not in an acceptable state (Is starting up, is shutting down or being terminated, or is in a locked state for sovereign/creative worlds).

  • a 503 status will also be returned if another request for the beacons is ongoing; in this case the response will include a Retry-After header with an approximate time until the API will be available to use again; there is no enforced fair-use of the endpoint though if querying the Boundless MMO world servers you should not expect to see this kind of response as the data will be cached at a higher level so that you simply get the older version of the data in the meantime instead.

How to decode the decompressed binary response

u16     : number of beacons (may actually end up with less than this many beacons after full parse)
u16     : world-size in terms of plots (width/height of the plot-map)
[
  u16   : zero (as in, literally a u16 with value 0); if this is not 0 then must exit the beacon-parsing loop now.
  --  # if u16 was zero:
  u8    : is this beacon a campfire? (1 or 0 value)
  i16[3]: position of the beacon master console / campfire in block-coordinates
  u8    : length of mayor-name string
  char[]: name of the beacon's mayor (UTF8)
  --  # if not a campfire:
  u64   : beacon prestige                       [ assume 0 for campfires    ]
  i8    : beacon compactness (-100 to 100)      [ doesnt apply to campfires ]
  u32   : number of 3d plots in the beacon      [ assume 2 for campfires    ]
  u32   : number of plot-columns in the beacon  [ assume 1 for campfires    ]
  u8    : length of name string                 [ doesnt apply to campfires ]
  char[]: name of the beacon (UTF8)             [ doesnt apply to campfires ]
] * N (N <= number of beacons)
# if you got less beacons than you were expecting, then the last u16 parsed will match the number of beacons missing.
#   this is an edge case due to asynchronous processing of the request if a beacon is deleted whilst processing the request
#   as the processing will check the number of beacons to build the response header first.

[
  u16   : index of beacon for this plot-column of the world; if 0 then there is no beacon here
          this indexes into the (1-indexed) list of beacons at the head of the response.
  u8    : number of 3d plots in this column
] * (world-size * world-size)
# the map data ordering matches the Lod0 Map request image with runs across in rows of equal-z
#   starting from the most negative plot coordinates

Example Requests

# request the beacons data and ask curl to decompress it
curl --compressed -H "Boundless-API-Key: TESTING_KEY" https://gs-testing-euc1.playboundless.com/13/api/beacons --output testing.euc1_t0_0.beacons.bin

Example Response Parsing

Below is an example Python 3 script that will parse the decompressed beacons data response above.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/env python
from argparse import ArgumentParser, FileType
from struct import unpack_from
import itertools

parser = ArgumentParser()
parser.add_argument("binary", help="binary files to parse", type=FileType("rb"), nargs="+", metavar="BIN")
args = parser.parse_args()

for f in args.binary:
    if len(args.binary) > 1:
        print(f"# {f.name}")

    buffer = f.read()

    index = 1
    offset = 0

    num_beacons, world_size = unpack_from("<HH", buffer, offset)
    offset += 4
    print(f"  <={num_beacons} beacons for world plot-size {world_size}")

    beacon_names = []
    for beacon_index in range(num_beacons):
        (skipped,) = unpack_from("<H", buffer, offset)
        offset += 2

        if skipped != 0:
            num_beacons -= skipped
            break

        campfire, pos_x, pos_y, pos_z, mayor_name_len = unpack_from("<BhhhB", buffer, offset)
        offset += 8
        (mayor_name,) = unpack_from(f"<{mayor_name_len}s", buffer, offset)
        mayor_name = mayor_name.decode("utf-8")
        offset += mayor_name_len

        if campfire != 0:
            print(f"  campfire #{beacon_index+1}:")
            print(f"      position    : {-pos_z}N {pos_x}E Altitude: {pos_y}")
            print(f"      mayor-name  : {mayor_name}")
            beacon_names.append("**Campfire")
        else:
            prestige, compactness, num_plots, num_plot_columns, name_len = unpack_from("<QbIIB", buffer, offset)
            offset += 18
            (name,) = unpack_from(f"<{name_len}s", buffer, offset)
            name = name.decode("utf-8")
            offset += name_len
            print(f"  beacon   #{beacon_index+1}:")
            print(f"      position    : {-pos_z}N {pos_x}E Altitude: {pos_y}")
            print(f"      name        : {name}")
            print(f"      mayor-name  : {mayor_name}")
            print(f"      prestige    : {prestige}")
            print(f"      compactness : {compactness}")
            print(f"      plots       : {num_plots}")
            print(f"      plot-columns: {num_plot_columns}")
            beacon_names.append(name)

    for z, x in itertools.product(range(world_size), repeat=2):
        beacon_index, plot_count = unpack_from("<HB", buffer, offset)
        offset += 3
        if beacon_index != 0:
            print(f"  ({x},{z}) : #{beacon_index} = {beacon_names[beacon_index - 1]} w/ {plot_count} plots")