Public endpoints your Raspberry Pi programs use to check for new releases, download the latest ZIP and stream their terminal output back to Freymi.
https://freymitwo.lovable.app8YM4U4JS).x-freymi-token header sent by older devices is ignored. Release endpoints need no authentication either./api/public/releases/{publicId}/latestReturns the newest release of the project. Pass your currently installed version to get an update_available flag.
| publicId | string(8) · required | Project code. |
| ?current | number (query) | Version currently installed on the device. Defaults to 0. |
curl -s "https://freymitwo.lovable.app/api/public/releases/<PROJECT_CODE>/latest?current=3"{
"project": "Greenhouse controller",
"public_id": "<PROJECT_CODE>",
"version": 4,
"update_available": true,
"url": "https://freymitwo.lovable.app/storage/.../v4.zip",
"latest_url": "https://freymitwo.lovable.app/api/public/releases/<PROJECT_CODE>/download",
"sha256": "9f2c...",
"size": 24680,
"files": 7,
"published_at": "2026-08-14T09:12:44.000Z"
}/api/public/releases/{publicId}/downloadStreams the ZIP archive of the latest release (binary response, content-type application/zip).
| publicId | string(8) · required | Project code. |
curl -L -o release.zip "https://freymitwo.lovable.app/api/public/releases/<PROJECT_CODE>/download"# binary ZIP body
Content-Type: application/zip/api/public/users/{userPublicId}/projectsLists every project of a user together with the latest version and download links.
| userPublicId | string(8) · required | User code. |
curl -s "https://freymitwo.lovable.app/api/public/users/<USER_CODE>/projects"{
"user": "<USER_CODE>",
"projects": [
{
"name": "Greenhouse controller",
"public_id": "<PROJECT_CODE>",
"version": 4,
"sha256": "9f2c...",
"latest_url": "https://freymitwo.lovable.app/api/public/releases/<PROJECT_CODE>/latest",
"download_url": "https://freymitwo.lovable.app/api/public/releases/<PROJECT_CODE>/download"
}
]
}Shared helper scripts and configuration files maintained by the Freymi administrators. No authentication is required — any device can list and download them.
/api/public/system-filesLists every system file with its size, checksum and direct download URL.
| ?prefix | string (query) | Optional folder filter, e.g. scripts/ — only paths starting with it are returned. |
curl -s "https://freymitwo.lovable.app/api/public/system-files?prefix=scripts/"{
"count": 1,
"files": [
{
"path": "scripts/watchdog.py",
"size_bytes": 2418,
"content_type": "text/x-python; charset=utf-8",
"checksum_sha256": "3b1a...",
"is_text": true,
"description": null,
"updated_at": "2026-08-14T18:20:11.000Z",
"download_url": "https://freymitwo.lovable.app/api/public/system-files/scripts/watchdog.py"
}
]
}/api/public/system-files/{path}Downloads a single system file. Nested folder paths are supported.
| path | string · required | Full file path from the listing, e.g. scripts/watchdog.py. |
curl -fsSL "https://freymitwo.lovable.app/api/public/system-files/scripts/watchdog.py" -o watchdog.py
python3 watchdog.py# raw file content (Content-Type matches the file)A log is simply an event belonging to a project — there are no runs. Post whatever you want to record and read it back from the same URL. No authorization is required; the 8-character project code is enough.
/api/public/logs/{publicId}Writes one event, a batch of events, or plain text (one line = one event).
| publicId | string(8) · required | Project code. |
| content-type | application/json | text/plain · required | text/plain turns every non-empty line into an info event. |
| message | string ≤4000 | Event text. Required. |
| level | "info" | "warn" | "error" | "debug" | Defaults to "info". Unknown values fall back to info. |
| source | string ≤120 | Optional label, e.g. script or device name. |
| ts | ISO 8601 | Time of the event. Defaults to the time of arrival. |
| events | array ≤200 | Batch form: array of objects with the fields above. |
# single event
curl -s -X POST "https://freymitwo.lovable.app/api/public/logs/<PROJECT_CODE>" \
-H "content-type: application/json" \
-d '{"message":"sensor ok: 21.4 C","level":"info","source":"main.py"}'
# batch
curl -s -X POST "https://freymitwo.lovable.app/api/public/logs/<PROJECT_CODE>" \
-H "content-type: application/json" \
-d '{"events":[{"message":"started"},{"message":"retry 1/3","level":"warn"}]}'
# plain text
python3 main.py 2>&1 | curl -s -X POST "https://freymitwo.lovable.app/api/public/logs/<PROJECT_CODE>" \
-H "content-type: text/plain" --data-binary @-{ "ok": true, "inserted": 2 }/api/public/logs/{publicId}Reads the newest events, optionally filtered.
| publicId | string(8) · required | Project code. |
| limit | integer 1-1000 | Number of events. Defaults to 200. |
| level | string | Return only events with this level. |
| since | ISO 8601 | Only events newer than this timestamp. |
| q | string | Substring match on the message. |
| format | "json" | "text" | text returns a plain-text tail. |
curl -s "https://freymitwo.lovable.app/api/public/logs/<PROJECT_CODE>?limit=50&level=error"
curl -s "https://freymitwo.lovable.app/api/public/logs/<PROJECT_CODE>?format=text&limit=100"{
"project": "<PROJECT_CODE>",
"count": 2,
"events": [
{ "id": 812, "ts": "2026-08-15T09:20:04.100Z", "level": "warn", "source": "main.py", "message": "retry 1/3" },
{ "id": 811, "ts": "2026-08-15T09:20:01.412Z", "level": "info", "source": "main.py", "message": "started" }
]
}A lightweight heartbeat: the device posts its current state, optionally tied to a project. The device is identified by its token (visible on the Devices page). No other authentication is required. Allowed statuses: started, running, stopped, quit, updating, new_version_downloaded, error, offline.
/api/public/status/device/{deviceToken}Stores one status report for the device.
| deviceToken | string · required | Device token. |
| content-type | application/json · required |
| status | string | One of the allowed statuses above. |
| project | string(8) | Optional project code the report belongs to. |
| message | string ≤2000 | Optional detail, e.g. an error text. |
| app_version | string ≤64 | Version running on the device. |
curl -s -X POST "https://freymitwo.lovable.app/api/public/status/device/<DEVICE_TOKEN>" \
-H "content-type: application/json" \
-d '{"project":"<PROJECT_CODE>","status":"running","app_version":"4"}'{
"ok": true,
"id": 128,
"device": "pi-greenhouse",
"status": "running",
"reported_at": "2026-08-15T12:04:11.221Z"
}/api/public/status/device/{deviceToken}Returns the most recent status reports for one device.
| deviceToken | string · required | Device token. |
| ?limit | integer (query) | Default 50, max 500. |
curl -s "https://freymitwo.lovable.app/api/public/status/device/<DEVICE_TOKEN>?limit=10"{
"device": "pi-greenhouse",
"count": 1,
"events": [
{ "id": 128, "status": "running", "message": "", "app_version": "4",
"reported_at": "2026-08-15T12:04:11.221Z", "project_id": "..." }
]
}/api/public/status/project/{publicId}Returns the most recent status reports linked to one project.
| publicId | string(8) · required | Project code. |
| ?limit | integer (query) | Default 50, max 500. |
curl -s "https://freymitwo.lovable.app/api/public/status/project/<PROJECT_CODE>?limit=10"{ "project": "<PROJECT_CODE>", "count": 1, "events": [ /* ... */ ] } status_retention_hours setting (default 48 hours).| 200 | OK | Request processed. |
| 400 | Bad request | Malformed JSON or body that fails validation. |
| 401 | Unauthorized | Reserved; no endpoint currently requires authentication. |
| 404 | Not found | Unknown project code, run id or no release yet. |
| 413 | Payload too large | More than 500 log lines in one batch. |
| 500 | Server error | Write to the database failed; retry later. |
Drop this module next to your program and call log() wherever you want to record something. A ready-to-copy version is in the project's Deployment tab.
1# freymi_log.py
2import json, socket, sys, urllib.error, urllib.request
3
4LOG_URL = "https://freymitwo.lovable.app/api/public/logs/<PROJECT_CODE>"
5SOURCE = socket.gethostname()
6
7def log(message, level="info", source=SOURCE):
8 """Record one event. Never raises — logging must not break the program."""
9 payload = {"message": str(message)[:4000], "level": level, "source": source}
10 req = urllib.request.Request(
11 LOG_URL,
12 data=json.dumps(payload).encode(),
13 headers={"content-type": "application/json"},
14 method="POST",
15 )
16 try:
17 with urllib.request.urlopen(req, timeout=10) as res:
18 return res.status < 400
19 except urllib.error.HTTPError as exc:
20 sys.__stderr__.write("[freymi] HTTP %s: %s\n" % (exc.code, exc.read()[:200]))
21 except Exception as exc:
22 sys.__stderr__.write("[freymi] log failed: %s\n" % exc)
23 return False
24
25log("Program started")
26log("Sensor read failed", level="error")