€0Free DEV offer, never a usage bill, on principle

Your game media, served from one link. No usage bill.

If your server can send an HTTP request, it gets a permanent URL back at once: images encoded during the request, video and audio compressed in place, the routes your existing scripts call answered as they are.

Works withFiveM RedM FiveM Enhanced Garry's Mod Minecraft Hytale Rust and any HTTP client
AGAperture GameOwner · FiveM
Assets Delivery
DEV 62%
31 GB of 50 GB
Aperture GameOverview
Live
Latest uploads1,172 uploads · 7 days
screenshot_1730041122.webp2 s ago · screenshot-basic · Lucas Ferreira 412 KB Ready
clip_police_poursuite.webm9 s ago · lb-phone production 18.4 MB Processing
vocal_dispatch_0412.ogg14 s ago · lb-phone production 86 KB Ready
appareil-photo_8812.webp31 s ago · lb-phone production 356 KB Ready
carte_cayo.png44 s ago · screenshot-basic · Léa Rousseau 684 KB Ready
avatar_inès.webp1 min ago · MDT police 64 KB Ready
response.json200 · 142 ms
{
  "status": "ok",
  "data": {
    "id": "01K2R7M8QF3V9XZ4B6NCDT0YAH",
    "url": "https://aperture-app.preview.moncompanion.cloud/aperture-game/01K2R7M8QF3V9XZ4B6NCDT0YAH.webp"
  },
  "meta": { "duration_ms": 142 }
}
Server-Timingas a waterfall in your devtools
app18 ms
db6 ms
encode97 ms
storage21 ms
screenshot_1730041122.webp412 KB · 2 s ago
Processing0:42
clip_police_poursuite.webm18.4 MB · 9 s ago
0:12
vocal_dispatch_0412.ogg86 KB · 14 s ago
appareil-photo_8812.webp356 KB · 31 s ago
carte_cayo.png684 KB · 44 s ago
avatar_inès.webp64 KB · 1 min ago
1:18
clip_braquage_fleeca.mp424.7 MB · 3 min ago
Failed
camera_7731.mov96.3 MB · transcode_failed
One key per resource, limited to what it needs.
KeyScopesUploads · 7 daysFailure rate
lb-phone productionapt_7Hq2kR4n · 2 s ago upload-imageupload-videoupload-audio 627 0.4% Normal
screenshot-basicapt_c4Rt9WbE · 12 min ago upload-image 384 1.1% Normal
MDT policeapt_Q8mZ2xLp · 1 min ago upload-image 142 6.8% Keep an eye on it Mostly insufficient_scope
CI assetsapt_Vn3K8sJd · 3 h ago manage-assets 19 0% Normal
Uploadaperture-app.preview.moncompanion.cloud/api/v1/uploads
API baseaperture-app.preview.moncompanion.cloud/api/v1
Deliveryaperture-app.preview.moncompanion.cloud
Quality82
From 40 to 100. The Balanced preset aims for 82.
Max box2560 × 1440
Original · PNG WebP · quality 82
Received1.6 MB Stored412 KB 74% saved

1920 × 1080, inside the max box: dimensions unchanged. A 480 px WebP thumbnail is made on the way.

Managed quotaDEV

31of 50 GB

62% usedAlerts at 80, 90 and 100%
Images 17 GB Video 11.5 GB Audio 2.5 GB
Server assets214 MB, every version includedOutside the quota
Stored, night after night Full around 9 Feb.
90% 80% Ceiling 50 GB Today · 31 GB

Least squares line over the last 28 nights: +180 MB a night. At 100%, uploads stop; delivery never does.

Billing

€0

billed by usage: not the traffic to players, not the uploads, not the transcoding.

Compatibility

4routes

v3, v2 and legacy API compatible routes, plus LBUpload, with the same responses.

Encoding

80-200ms

for a 1080p capture to WebP. The URL returned is already the final one.

Delivery

100%

of the files online stay served when the quota is full. Only new uploads are refused.

Figures taken from Aperture's code and configuration, not from a campaign. Encoding time depends on the machine hosting the instance.

Works with

One HTTP API, whatever the game. If your server can send a request, it can upload.

Samples for each game
  • Ready-made

    FiveM, RedM and FiveM Enhanced

    PerformHttpRequest

    lb-phone, screenshot-basic and the routes your scripts already call, answered as they are.

    Quickstart
  • Via HTTP

    Garry's Mod

    HTTP()

    GLua, from a server-side addon.

    See the sample
  • Via HTTP

    Minecraft

    HttpClient

    Java, from a Paper or Spigot plugin, or a Fabric mod.

    See the sample
  • Via HTTP

    Hytale

    HttpClient

    Java, from a server plugin.

    See the sample
  • Via HTTP

    Rust

    webrequest.Enqueue

    C#, from an Oxide or Carbon plugin.

    See the sample
  • Via HTTP

    Anything else

    POST /api/v1/uploads

    A Discord bot, a website or your own engine: multipart or base64, on the same two endpoints.

    See the sample

Game names belong to their owners and only say which platforms Aperture's API works with. Aperture is not affiliated with or endorsed by them.

How it works

Four steps to the first link. The third one is copy and paste.

A name, a platform. The short identifier goes into every URL.

New organisationStep 1 of 4
Platform

Fixed for life. A media link resolves through the file's identifier: renaming the organisation breaks none of them.

https://aperture-app.preview.moncompanion.cloud/aperture-game/01K2R7M8QF3V9XZ4B6NCDT0YAH.webp
You become its owner.

One key per resource. Limited to what it needs.

New keyStep 2 of 4
Permissions

A key never carries more than its author is allowed to do.

Copy it now: it will not be shown again.
120 requests a minute per key, adjustable.

The API keys screen writes it for you. With your URLs, and your key as soon as it is created.

-- lb-phone/shared/upload.lua, in UploadMethods.Custom
Image = {
  url = "https://aperture-app.preview.moncompanion.cloud/api/v1/uploads",
  field = "file",
  headers = { ["Authorization"] = "API_KEY" },
  error = { path = "status", value = "error" },
  success = { path = "data.url" },
},
-- Video and Audio: same block; leave API_KEY, lb-phone replaces it

-- lb-phone/server/apiKeys.lua, one key per type, out of shared/
-- the uploading player still receives it: scope it to uploads only
API_KEYS = { Image = "apt_7Hq2kR4n_k3Vb9QzT..." }

-- lb-phone/config/config.lua
Config.UploadMethod.Image = "Custom"
-- add the delivery host to the existing list
Config.UploadWhitelistedDomains = { "aperture-app.preview.moncompanion.cloud" }
-- server.lua : PerformHttpRequest has no multipart, so base64 it is
PerformHttpRequest("https://aperture-app.preview.moncompanion.cloud/api/v1/uploads/base64", function(status, body)
  local response = json.decode(body)
  if status == 200 then print(response.data.url)
  else print("upload refused: " .. response.error.code) end
end, "POST", json.encode({ base64 = dataUri, filename = "capture.png" }), {
  ["Content-Type"] = "application/json",
  ["Authorization"] = GetConvar("aperture_key", ""),
  ["Idempotency-Key"] = captureId, -- a retry never creates a duplicate
})
# What the key may do, and the quota left
curl https://aperture-app.preview.moncompanion.cloud/api/v1/token \
  -H "Authorization: Bearer $APERTURE_KEY"

# Upload a file, with no duplicate if the response gets lost
curl https://aperture-app.preview.moncompanion.cloud/api/v1/uploads \
  -H "Authorization: Bearer $APERTURE_KEY" \
  -H "Idempotency-Key: capture-4471" \
  -F "file=@screenshot_1730041122.webp"

Without aperture-app.preview.moncompanion.cloud in UploadWhitelistedDomains, the upload succeeds but the photo never shows up in the phone.

Your server uploads a file. The dashboard sees it arrive.

Latest uploadsLive
screenshot_1730041122.webp2 s ago · screenshot-basic · Lucas Ferreira 412 KB Ready
clip_police_poursuite.webm9 s ago · lb-phone production 18.4 MB Processing
vocal_dispatch_0412.ogg14 s ago · lb-phone production 86 KB Ready
appareil-photo_8812.webp31 s ago · lb-phone production 356 KB Ready
https://aperture-app.preview.moncompanion.cloud/aperture-game/01K2R7M8QF3V9XZ4B6NCDT0YAH.webp

An image is final as soon as the response comes back. A video plays at once, then the compressed file replaces it at the same address.

File types

One treatment per type

The type is read from the bytes, never from the extension. Each one has its formats, its limit and its encoding.

JPEG · PNG · AVIF → WebP

Image

32 MB max
  • InputJPEG, PNG, WebP, GIF, AVIF, BMP, TIFF. SVG always refused.
  • OutputWebP quality 82 by default, or AVIF, JPEG, PNG, or the original.
  • TimingEncoded during the request: the URL returned is final.
Image formats
MP4 · WebM · MOV → H.264

Video

512 MB max
  • InputMP4, WebM, MOV, MKV, MPEG, Ogg.
  • OutputH.264 CRF 26 by default, or VP9, AV1; 1080p and 60 fps at most.
  • TimingPlayable at once, then compressed in place, at the same URL.
Video formats
MP3 · WAV · Ogg → Opus

Audio

64 MB max
  • InputMP3, Ogg, Opus, WAV, AAC, M4A, FLAC.
  • OutputOpus 96k mono, loudness normalised (EBU R128); AAC or MP3 if you prefer.
  • TimingServed from the response on, replaced in place once processed.
Audio formats
logos/logo-principal

Server assets

Outside the quota
  • ContentLogos, interface images and sounds, sorted by category.
  • AddressA stable URL serving the current version; roll back in one call.
  • LifetimeUnlimited versions, never purged. Single-use NUI tickets, 60 s.
Server assets guide
Product preview · sample data

What your dashboard sees. Every upload, the second it arrives.

Aperture Game · live
LFLucas Ferreirascreenshot_1730041122.webp412 KB 2 s ago
LPlb-phone productionclip_police_poursuite.webm18.4 MB 9 s ago
LPlb-phone productionvocal_dispatch_0412.ogg86 KB 14 s ago
LPlb-phone productionappareil-photo_8812.webp356 KB 31 s ago
LRLéa Rousseaucarte_cayo.png684 KB 44 s ago

The player name comes from the player-metadata header, when the script uploading the file sends it; lb-phone 2.9.0 does not. Nothing checks it: the dashboard shows it as "Supplied by the client".

The model

The quota is fixed. So is the bill.

Every organisation has a ceiling in GB, known in advance. You are warned at 80, 90 and 100%. At the ceiling, new uploads get quota_exceeded and everything online stays served.

AGAperture Gameexample
31 GB of 50 GB62%
Full around 9 February, at the current pace
80% · alert 90% · alert 100% · uploads refused Aperture Game · 62%

A ceiling in GB, known in advance.

An alert at 80, 90 and 100%.

At the ceiling, only uploads stop.

Traffic is measured, never billed.

€0 billed by usage.

A new organisation's offerDEV · 50 GB · 5 GB/month
On your FTP server, on requestNo ceiling
Counter recalculatedEvery night at 03:20
Features

What a production server asks for. Nothing billed by volume.

Read the documentation
Received1.6 MB
Stored412 KB
WebP quality 82 · 74% saved

Tunable compression

Format, quality and maximum size per type, with a before and after test bench on your own images.

media.aperture-game.fr Waiting for DNS
TXT_aperture.mediaaperture-verify=7f3kq9

Your domain

Soon

Your media served from your own domain: TXT verification, certificate issued on the first request. Links already shared keep working.

AMILKBJF+1 invitation
View media Granted Manage storage Reserved

Members and permissions

13 permissions per member, from View media to Manage storage. An organisation always keeps an owner.

upload-imageupload-video
delete-mediarequires Delete media

Key scopes

A key never carries more than its author can do. Allowed addresses and a rate limit per key.

Mon-Sun · 0-23 h

Hour by hour statistics

Uploads, deliveries, outgoing traffic and refusals. What is not measured reads "Not measured", never zero.

A 7 day trash before anything is erased

Retention your way

Nothing is deleted by default. Fixed age or unused files, your choice; a file can be kept forever.

POST /api/v1/uploads
GET /api/v1/token
GET /api/v1/status

OpenAPI reference

Generated from openapi.yaml, with one page per error code and an llms.txt. Documentation in English.

An account follows its browser until it picks.

French and English

The dashboard speaks both languages. The API answers in English, with stable codes that are never translated.

Comparison

What changes when nothing is counted. Line by line.

Compare the offers
Aperture compared with a host billed by usage, criterion by criterion.
Criterion Aperture Host billed by usage
Billing
Amount known in advance, whatever the traffic Yes, Fixed monthly price No
Traffic to players with no meter to pay Yes, Measured, never billed No
Quota full: files online still served Yes, Only uploads stop Not verified
Integration
v3, v2, legacy API and LBUpload compatible routes Yes, Same responses Not verified
Files stored on your FTP or FTPS server Yes, On request Not verified
Server assets at a stable URL, outside the quota Yes, Unlimited versions Not verified
Custom domain PartialRolling out Not verified
Diagnostics
Stable error codes, one doc page per code Yes, 34 documented codes Not verified
Retry without duplicates (Idempotency-Key) Yes, Key kept 24 h Not verified

Right-hand column: the usage billing model, described from public offers. Not verified: a point we have not checked against a public offer, so we claim nothing either way. No brand is named: the column describes a model, not a competitor.

"No feature will ever bring usage billing back. It is the reason Aperture exists."
The Aperture teamThe project's founding rule
Migration

Moving from another host? Change the host, keep your Lua.

Aperture answers on the paths your scripts already call, with the same response bodies; lb-phone 2.9.0 moves to its Custom method instead. To roll back, put the old configuration back: nothing rewrites your database.

server.lua2 lines
-local uploadUrl = "https://old-host.example/api/v3/file/base64"
+local uploadUrl = "https://aperture-app.preview.moncompanion.cloud/api/v3/file/base64"
-local uploadKey = GetConvar("old_host_key", "")
+local uploadKey = GetConvar("aperture_key", "")
PerformHttpRequest(uploadUrl, onUploaded, "POST", body, {
["Authorization"] = uploadKey, ["Content-Type"] = "application/json" })
Routes answered, same responses
/api/v3/file/api/v3/file/base64/api/v2/{image,video,audio}/api/{image,video,audio}/lb-upload
Your old files stay with the previous provider, at their current URLs.
Frequently asked questions

The questions we get asked. The answers from the product.

No, on principle. Every offer has a fixed monthly price, with storage and bandwidth included. An alert at 80 then 100% of the included bandwidth; past it, never a bill or a cut-off: your media stay served. The number of uploads and transcoding are never billed.

No. FiveM, RedM and FiveM Enhanced get ready-made integrations (lb-phone, screenshot-basic and the routes your scripts already call), but the API itself is plain HTTP: a Garry's Mod addon, a Minecraft or Hytale plugin, a Rust plugin, a Discord bot or your own engine uploads the same way, in multipart or in base64. The guide for other games gives a sample for each.

Not with lb-phone 2.9.0: at every upload, its hosted methods ask the host for an address, so it moves to its Custom method. Its configuration changes, not its code, and the API keys screen writes that configuration for you. Scripts calling the v3, v2, legacy API or LBUpload routes, including older lb-phone releases whose blocks post to them, change the host and the key, nothing else: Aperture sends back the same responses there.

New uploads are refused with the code quota_exceeded (HTTP 413). You are warned before, at 80 then 90%. Files already online stay served: delivery never stops.

They stay where they are. Aperture does not reach into another provider's storage; your old links work as long as that account exists. Only new uploads come to us.

Yes, over FTP or FTPS, on request and with any offer: the instance's operator enables this setting for your organisation. Aperture checks every write, serves the files from its host or your domain, and applies no ceiling.

Forever, by default. You can choose retention by age or by disuse; any deletion then goes through a 7 day trash, and a file can be marked "Keep forever".

Up to 32 MB per image, 512 MB per video and 64 MB per audio. Beyond that, the upload is refused with file_too_large. SVG is always refused, whatever its size.

Revoke it: uploads using it fail at once with token_revoked. A key limited to upload-image cannot delete anything, and its rate limit is its own.

Still have questions?

The guides cover the integration end to end, script by script.

Read the guides

Your next upload will have its link. Create the organisation, paste the configuration.

POST /api/v1/uploads → https://aperture-app.preview.moncompanion.cloud/aperture-game/01K2R7M8QF3V9XZ4B6NCDT0YAH.webp