6.4 KiB
UpdateForge API
Base URL: https://<your-server>
All API routes are under /api/v1.
All request and response bodies are JSON.
Authentication
UpdateForge uses Bearer token authentication. Obtain a token via the login endpoint and include it in every subsequent request:
Authorization: Bearer <token>
Tokens are long-lived JWTs signed by the server. Store them securely (e.g. Android EncryptedSharedPreferences). There is no built-in expiry endpoint — simply re-authenticate when you receive a 401.
Endpoints
POST /api/v1/auth
Authenticate with email and password. Does not require an existing token.
Request
{
"email": "user@example.com",
"password": "secret"
}
Response 200
{
"token": "eyJ...",
"userId": "abc123",
"email": "user@example.com"
}
Error responses
| Status | Body | Reason |
|---|---|---|
400 |
{"error": "email and password are required"} |
Missing fields |
401 |
{"error": "invalid credentials"} |
Wrong email or password |
GET /api/v1/apps
Returns all teams the authenticated user belongs to, each with its apps. A user may belong to multiple teams. Within each app, the response includes the latest current and pending version per build type.
- current — the highest semver version that has an artifact file attached. Always present (may be
nullif none exists yet). - pending — the highest semver version with no artifact file yet (work in progress).
nullwhen none exists.
Teams and apps are ordered oldest-first by creation date.
Response 200
{
"teams": [
{
"id": "team_id",
"name": "Acme",
"apps": [
{
"id": "app_id",
"title": "My App",
"packageName": "com.example.myapp",
"icon": "/api/files/apps/app_id/icon.png",
"release": {
"current": {
"id": "ver_id",
"version": "1.2.0",
"name": "January update",
"public": true,
"hasFile": true,
"created": "2026-01-15T10:30:00Z"
},
"pending": null
},
"debug": {
"current": {
"id": "ver_id_2",
"version": "1.3.0",
"name": "",
"public": false,
"hasFile": true,
"created": "2026-01-20T08:00:00Z"
},
"pending": {
"id": "ver_id_3",
"version": "1.4.0",
"name": "Next sprint",
"public": false,
"hasFile": false,
"created": "2026-01-21T09:00:00Z"
}
},
"profile": {
"current": null,
"pending": null
}
}
]
}
]
}
teams is an empty array if the user belongs to no teams.
Version object fields
| Field | Type | Description |
|---|---|---|
id |
string | Version record ID — use this for downloads |
version |
string | Semantic version string, e.g. "1.2.0" |
name |
string | Optional human-readable label, empty string if not set |
public |
bool | Whether this version is intended for all testers |
hasFile |
bool | Whether an artifact is available for download |
created |
string | ISO 8601 UTC timestamp |
Error responses
| Status | Body | Reason |
|---|---|---|
401 |
{"error": "unauthorized"} |
Missing or invalid token |
GET /api/v1/versions/{id}/download
Download the artifact file for a version. {id} is the version's id field from the apps response.
The response is the raw file with Content-Type: application/octet-stream and a Content-Disposition: attachment header carrying the original filename. Supports HTTP range requests, so partial downloads and resume work out of the box.
Error responses
| Status | Body | Reason |
|---|---|---|
401 |
{"error": "unauthorized"} |
Missing or invalid token |
403 |
{"error": "forbidden"} |
User is not a member of the version's team |
404 |
{"error": "version not found"} |
ID does not exist |
404 |
{"error": "no file attached"} |
Version exists but hasFile is false |
Build types
Every version belongs to exactly one build type:
| Type | Intended use |
|---|---|
release |
Production-ready builds for general testers |
debug |
Debug builds with logging/tooling enabled |
profile |
Performance-profiling builds |
Icons
The icon field in the app object is a root-relative URL path. Prepend the server base URL to fetch it:
GET https://<your-server>/api/files/apps/<app_id>/<filename>
Icon files are served publicly and do not require authentication.
Typical client flow
1. POST /api/v1/auth → store token
2. GET /api/v1/apps → display teams and their apps; cache version IDs
3. For each app, compare installed version string with current.version
4. If update available and current.hasFile == true:
GET /api/v1/versions/{id}/download → save & install APK
Error format
All error responses share the same shape:
{ "error": "<human-readable message>" }