# BuildHut > Binary distribution platform for Android, Linux, and Windows apps. BuildHut is a self-hosted binary distribution platform. Developers upload application builds (APK, AAB, EXE, MSI, MSIX, AppImage, DEB, RPM, Flatpak, Snap, tar.zst, tar.gz, tar.xz) and share them through public download pages with developer profiles, organization management, and telemetry. ## Website https://hut.buildr.fun ## Source Code https://git.sr.ht/~drzoidberg/Buildhut ## Contact Telegram: https://t.me/+8lgWL2r63mk5Zjhk ## Public Pages - / - Landing page with recent builds, features overview, and navigation - /apps - Browse all apps with search, filtering by organization, and pagination - /apps/{name} - Individual app page with builds, versions, and download links - /devs - Browse all developers with search and pagination - /devs/{name} - Developer profile with apps, organization membership, and stats - /orgs - Browse all organizations with search and pagination - /orgs/{name} - Organization profile with team members and apps - /submit - Upload a new build (requires bot challenge or auth) - /login - Developer authentication page - /privacy - Privacy policy (GDPR compliant) - /terms - Terms of service - /support - Support page with Telegram community link ## Authentication - Session cookies: Set after bot challenge or login (dev-id, session-token) - API tokens (Bearer): Generate at /portal/tokens. Format: bh_ + 64 hex. Up to 5 per dev. - App secrets: 32-char hex for telemetry/OTLP. Passed via X-Secret header, ?secret= query, or body. ## API Endpoints ### App & Build APIs - GET /api/app/{id} - Latest build info + presigned download URL (public, 5-min cache). Response: { app, version, architecture, tag, description, tags, downloadUrl, uploadedAt } - GET /api/apps/{name}/latest - 303 redirect to latest build download (public) - GET /api/apps/{name}/latest/{os}/{arch}/{format} - Platform-specific download (public). Append /signature or .sig/.asc suffix on format to get PGP signature. - POST /api/upload/presign - Get presigned S3 upload URL (auth: session/bearer) - POST /api/upload - Finalize build upload (auth: session/bearer, max 200MB). Supports signature and signatureS3Key fields for PGP signatures. Upload a .sig/.asc file directly to attach a signature to an existing build. ### Directory APIs - GET /api/apps - List all apps with search, org filter, pagination (public). Query: search, org, page. Response: { apps: [{ id, name, description, iconUrl, tags, buildCount, platforms, dev, org }], pagination } - GET /api/apps/{name} - Full app detail with builds, banner, dev, org, related apps (public). Response: { app, builds, banner, otherAppsByDev, otherAppsByOrg } - GET /api/devs - List all developers with search, org filter, pagination (public). Query: search, org, page. Response: { devs: [{ id, name, email, avatarUrl, appCount, totalDownloads, recentApps, org }], pagination } - GET /api/devs/{name} - Developer detail with apps, stats, PGP key, org colleagues (public). Response: { developer, apps, stats, orgColleagues } - GET /api/orgs - List all organizations with search, pagination (public). Query: search, page. Response: { orgs: [{ id, name, handle, description, appCount, devCount, recentApps }], pagination } - GET /api/orgs/{name} - Organization detail with apps, member developers, stats (public). Response: { org, apps, developers, stats } ### Telemetry API - POST /api/telemetry - Submit telemetry events (auth: app secret via X-Secret header or body) - GET /api/telemetry - List telemetry events (auth: session, query: appName, kind, limit) - GET /api/telemetry/stream - Telemetry stream health probe (auth: session; 403 when unauthenticated). WebSocket upgrades on this same path are handled outside SvelteKit (server-level `upgrade` handler) ### OTLP Endpoints (OpenTelemetry Protocol) - POST /v1/metrics - Ingest OTLP metrics (auth: app secret, JSON/Protobuf) - POST /v1/logs - Ingest OTLP logs (auth: app secret, JSON/Protobuf) - POST /v1/traces - Ingest OTLP traces (auth: app secret, JSON/Protobuf) ### Feature Flags API - GET /v1/flags - Fetch feature flag definitions (auth: app secret) - POST /v1/flags/eval - Report flag evaluations (auth: app secret) ### Publishing APIs - POST /api/publish/generate - Preview publishing files (auth: session) - POST /api/publish/publish - Execute publish run (auth: session) - GET /api/publish/status/{runId} - Poll publish status (auth: session) - GET /api/publish/ssh-keys - List SSH keys (auth: session) - POST /api/publish/ssh-keys - Add SSH key (auth: session, encrypted at rest) - DELETE /api/publish/ssh-keys/{id} - Delete SSH key (auth: session) - POST /api/publish/rotate-key - Rotate encryption keys (auth: admin only) ### Utility APIs - GET /api/auth/status - Check auth state (returns { authenticated: bool, user }) - GET|POST /api/logout - Clear session (POST returns JSON, GET redirects to /) - GET /api/img/{key} - Serve uploaded images (public) - GET /api/branches?app={name} - List git branches (auth: session) - POST /api/pgp/fetch-key - Fetch PGP key from keys.openpgp.org (auth: session) - POST /api/validate - Validate bot challenge (public) - GET /api/svg-challenge?token=... - Bot challenge SVG image (public) - GET /api/debug/builds - List all builds (dev only, disabled in production) ## Features - Direct-to-cloud uploads via presigned S3 URLs (max 200 MB) - Multi-platform support: Android (APK, AAB), Linux (AppImage, DEB, RPM, Snap, tar.zst, tar.gz, tar.xz), Windows (EXE, MSI, MSIX) - PGP signature support for builds (upload .asc/.sig, download via /signature suffix) - API tokens with custom expiry for CI/CD integration - Interactive SVG bot protection (no third-party captcha) - Real-time telemetry and analytics per app - OpenTelemetry Protocol (OTLP) ingestion for traces, metrics, and logs - Feature flag management with evaluation tracking - AUR and F-Droid publishing with SSH key management - Developer profiles with public pages - Organization management with team membership - Session-based authentication with token support ## Supported File Types .apk, .aab, .exe, .msi, .msix, .appimage, .deb, .rpm, .flatpak, .snap, .bin, .tar.zst, .tar.gz, .tar.xz, .tgz ## Crawling Notes - All pages under /portal/* require authentication - App, developer, and organization detail pages use dynamic URLs based on their name - Pagination uses ?page=N query parameter - Search uses ?search=query query parameter - Organization filtering uses ?org=name query parameter - **GET /api/apps** - List apps ; auth: public, query params Public directory listing with substring search, organisation filter and pagination (page size 20, ordered by updatedAt desc). Cache-Control: public, max-age=120. - 200: Paginated list of apps (json) - 500: Unable to list apps (json) - **GET /api/apps/{name}** - Get app detail ; auth: public (path: name) Full app detail with all builds, banner, developer and organisation info, plus up to 5 other apps by the same developer / organisation. Cache-Control: public, max-age=120. - 200: App detail with builds, banner and related apps (json) - 404: App not found (json) - 500: Unable to load app (json) - **GET /api/apps/{name}/latest** - Download latest build (redirect) ; auth: public (path: name) 302/303 redirect to a presigned S3 download URL for the latest build (any platform). A catch-all suffix ({os}[/{arch}[/{format}[/signature]]]) selects the platform - see the platform param. Increments the download counter with a 30s per-IP debounce. - 303: Redirect to presigned S3 URL (Location header) - 404: App not found, or no build matching the platform - 500: Storage misconfiguration - **POST /api/upload** - Upload build (direct) / finalize presigned upload ; auth: bearer ; body: application/json (required) | multipart/form-data (required) Two flows by content type: (1) multipart/form-data with a `file` field streams the binary straight to S3 (max 200 MB, supported extensions apk/aab/exe/msi/msix/appimage/deb/rpm/flatpak/snap/appbundle/bin/tar.zst/tar.gz/tar.xz/tgz); (2) JSON (or form without file) finalizes an earlier presigned PUT - s3Key must start with builds/. App name is lowercased server-side. Auth: Bearer API token (bh_...) via hooks or a dev session. - 201: Build record created (json) - 400: Missing/invalid fields, unsupported extension, bad icon or signature file (json) - 401: Authentication required (json) - 402: Build limit reached (plan upgrade needed) (json) - 404: No build to attach a signature-only upload to (json) - 413: File exceeds 200 MB limit (json) - 500: Storage or icon upload failure (json) - **POST /api/upload/multipart** - Chunked upload API (large artifacts) ; auth: bearer ; body: application/json (required) JSON action API that bypasses the Cloudflare proxy's ~100 MB request-body cap: `init` ({kind: binary|signature, name, version, fileName, size, contentType?, parts?}) returns {uploadId, key, partSize, partUrls[], expiresIn}; PUT every part to S3 directly and collect ETags (retry one part with `part-url`); `complete` {uploadId, parts:[{partNumber,etag}]} finishes the S3 upload and creates the build rows exactly like POST /api/upload (for kind=signature complete takes {uploadId} only); `abort` abandons the upload. Auth: Bearer token or dev session. - 200: part-url / abort / signature-complete result (URL, aborted id, or updated-signature build) (json) - 201: Init session created, or binary complete finalized (json) - 400: Validation error (json) - 401: Authentication required (json) - 404: Unknown or expired uploadId; no build to attach signature (json) - 413: File exceeds 200 MB limit (json) - 500: S3/storage failure or signature verification failure (json) - **GET /api/branches** - List git branches of an app's source repo ; auth: session, query params Lists branches from the app's source host (GitHub/SourceHut) using the developer's stored OAuth token; requires the app query parameter and ownership of the app (session or Bearer token). - 200: Branch list (json) - 400: Missing app parameter (json) - 403: Not authenticated or not the owner of the app (json) - 404: App not found (json) - **POST /api/pgp/fetch-key** - Look up a PGP public key from keys.openpgp.org ; auth: session ; body: application/json (required) Query by email address or key ID / fingerprint. Returns the armored public key with optional key ID and uid. Requires a dev session or Bearer token. - 200: PGP key details (json) - 400: Invalid JSON or missing query field (json) - 401: Authentication required (json) - 404: Key not found on keys.openpgp.org (json) - 502: Keyserver lookup failed (json) - **POST /api/telemetry** - Log a telemetry event ; auth: secret ; body: application/json (required) Authenticated with the app's 32-char hex telemetry secret via the X-Secret header, ?secret= query, or a body `secret` field. appName may arrive via ?appName= or the body. Payload validation re-uses the route's telemetryIn schema: kind is one of error/warning/info/trace/debug (default info) and bits is any JSON value. Responds { ok: true } with 201 when accepted. - 201: Telemetry accepted (json) - 400: Missing appName or invalid payload (json) - 401: Missing or invalid secret; app telemetry not configured (json) - 404: App not found (json) MCP tool surfaces (pure data, exported from scripts/contract/registry.ts as `tools`, rendered under `x-tools` in openapi.json): list_apps, get_app, start_build_upload, authorize_build_upload, fetch_pgp_key, submit_telemetry.