DocumentationAPI v1
Guide · Configuration

Migrating to Aperture

Moving scripts that already upload elsewhere: what changes, what does not, and how to roll back.

The short version

Scripts that call the routes below change the base URL and the key, nothing else. lb-phone 2.9.0 is the exception: it switches to its Custom method, which is a change to its configuration rather than to its code.

Aperture answers on the exact paths deployed scripts already call, in the exact shape they already parse. There is no SDK to swap and no script to rewrite: screenshot-basic calls and the upload scripts written against these routes work as they are once the host and the key change.

Route your scripts call Here
POST /api/v3/file same path, same body
POST /api/v3/file/base64 same path, same body
POST /api/v2/{image,video,audio} same path, same body
POST /api/{image,video,audio} same path, same body
lb-upload base URL POST /lb-upload

The oldest of these APIs is documented nowhere any more. Its shape was read out of an older lb-phone shared/upload.lua rather than out of documentation, which is how we found that it puts url at the root of the response rather than under data, and that its audio form field is named recording.

lb-phone: switch to the Custom method

This applies to lb-phone 2.9.0, the release these instructions were written against, and to any release whose upload method for your old host holds url = "PRESIGNED_URL" in lb-phone/shared/upload.lua.

In 2.9.0 every hosted upload method holds that line: lb-phone asks the host for an upload address at each upload, so the block has no address of its own to change, no headers and no error path. Typing an Aperture URL in place of PRESIGNED_URL in your old host's block does not bring a single photo here: the upload either still goes to your old host, or reaches Aperture without an Authorization header and is refused with missing_token. The Custom method is the one that carries both the URL and the key.

In lb-phone/shared/upload.lua, inside UploadMethods.Custom, give each kind the Aperture URL, the Authorization header and the two response paths:

Lua
Custom = {
    Video = {
        url = "https://your-instance/api/v1/uploads",
        field = "file",
        headers = { ["Authorization"] = "API_KEY" },
        error   = { path = "status", value = "error" },
        success = { path = "data.url" },
    },
    Image = {
        url = "https://your-instance/api/v1/uploads",
        field = "file",
        headers = { ["Authorization"] = "API_KEY" },
        error   = { path = "status", value = "error" },
        success = { path = "data.url" },
    },
    Audio = {
        url = "https://your-instance/api/v1/uploads",
        field = "file",
        headers = { ["Authorization"] = "API_KEY" },
        error   = { path = "status", value = "error" },
        success = { path = "data.url" },
    },
}

Leave API_KEY exactly as written. It is a placeholder, listed at the top of upload.lua among the ones lb-phone fills in, and lb-phone puts the key of that kind in its place. success.path is where our response carries the URL, and error.path with value = "error" is how the phone tells a refusal apart.

In lb-phone/server/apiKeys.lua, put an Aperture token in place of each old key. The table is keyed by kind, and each upload uses the key of its own kind:

Lua
API_KEYS = {
    Video = "YOUR_VIDEO_TOKEN",
    Image = "YOUR_IMAGE_TOKEN",
    Audio = "YOUR_AUDIO_TOKEN",
}

Give each token the upload scope of its own kind, and nothing else. This server-side file keeps the keys out of shared/upload.lua, which is sent to every player who joins, so a key written there can be read out of any player's cache. It does not keep them from players: lb-phone sends each upload from the player's game, so the key of that kind reaches the game of the player who uploads, and a curious player can read it there. Treat these tokens as exposed: upload scope only, one per kind, used by no other script, and revoked and replaced from the dashboard as soon as one turns up where it should not.

In lb-phone/config/config.lua, move the three kinds to Custom:

Lua
Config.UploadMethod.Video = "Custom"
Config.UploadMethod.Image = "Custom"
Config.UploadMethod.Audio = "Custom"

Then add your delivery host to Config.UploadWhitelistedDomains, in the same file, as the lb-phone guide explains: without it the upload succeeds and the photo never appears in the phone.

Other scripts: switch the base URL

A screenshot-basic call or a Lua script posting with PerformHttpRequest keeps its path. Change the host in front of it, and put an Aperture token where the old key was:

Lua
exports['screenshot-basic']:requestScreenshotUpload(
    'https://your-instance/api/v3/file',
    'file',
    { headers = { Authorization = 'YOUR_TOKEN' } },
    function(data)
        print(json.decode(data).data.url)
    end
)

A key passed in the query string as ?apiKey= is read the same way here. The screenshot-basic guide covers both forms and the error codes worth branching on.

If you are rewriting a script anyway, point it at /api/v1/uploads: it is the API we document, and it puts no compatibility layer between your script and the response.

What happens to your old files

They stay where they are. Aperture does not reach into another provider's storage, and your existing URLs keep working as long as that account does.

Plan for that: the reason to move is usually that the other account is going away, and a link written into a phone database outlives the decision to migrate.

Rolling back

Put the old URL and the old keys back. For lb-phone, set Config.UploadMethod back to the method you used before and put the old keys back in server/apiKeys.lua; the Custom block can stay, since nothing uses it once no kind names it.

Nothing here rewrites your game server database, and files uploaded to Aperture keep their URLs whether or not anything still points here.

Two things a script gains here

Who uploaded what. A script can send a player-metadata header with an upload. When it does, Aperture records it and the library shows the player behind that screenshot, with nothing to set up on this side.

Retries stop costing twice. PerformHttpRequest has no dependable timeout, so a lost response is a real possibility. Send an Idempotency-Key, one value per upload, and a retry returns the first upload instead of storing a duplicate.

lb-phone gains neither from its Custom block. Its headers are the same fixed strings on every upload, so it cannot send a key per upload, and it does not retry a failed upload anyway. Never add an Idempotency-Key to those headers: for a day, every later photo, clip and voice note would come back with the first one's URL. lb-phone could send the player as PLAYER_DATA in a player-metadata header, but the phone's interface runs in the game's browser, which refuses to send a header holding a character outside Latin-1 (the Western European character set): a player whose name has an emoji or a Cyrillic letter could not upload at all, and accented letters such as é arrive in a form Aperture cannot decode. The Custom block above leaves it out for that reason.