Back

API documentation

Public endpoints your Raspberry Pi programs use to check for new releases, download the latest ZIP and stream their terminal output back to Freymi.

Basics

Releases

GET
/api/public/releases/{publicId}/latest

Returns the newest release of the project. Pass your currently installed version to get an update_available flag.

Path parameters

publicIdstring(8) · requiredProject code.

Request body (JSON)

?currentnumber (query)Version currently installed on the device. Defaults to 0.

Example request

curl -s "https://freymitwo.lovable.app/api/public/releases/<PROJECT_CODE>/latest?current=3"

Example response

{
  "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"
}
GET
/api/public/releases/{publicId}/download

Streams the ZIP archive of the latest release (binary response, content-type application/zip).

Path parameters

publicIdstring(8) · requiredProject code.

Example request

curl -L -o release.zip "https://freymitwo.lovable.app/api/public/releases/<PROJECT_CODE>/download"

Example response

# binary ZIP body
Content-Type: application/zip
GET
/api/public/users/{userPublicId}/projects

Lists every project of a user together with the latest version and download links.

Path parameters

userPublicIdstring(8) · requiredUser code.

Example request

curl -s "https://freymitwo.lovable.app/api/public/users/<USER_CODE>/projects"

Example response

{
  "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"
    }
  ]
}

System files

Shared helper scripts and configuration files maintained by the Freymi administrators. No authentication is required — any device can list and download them.

GET
/api/public/system-files

Lists every system file with its size, checksum and direct download URL.

Request body (JSON)

?prefixstring (query)Optional folder filter, e.g. scripts/ — only paths starting with it are returned.

Example request

curl -s "https://freymitwo.lovable.app/api/public/system-files?prefix=scripts/"

Example response

{
  "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"
    }
  ]
}
GET
/api/public/system-files/{path}

Downloads a single system file. Nested folder paths are supported.

Path parameters

pathstring · requiredFull file path from the listing, e.g. scripts/watchdog.py.

Example request

curl -fsSL "https://freymitwo.lovable.app/api/public/system-files/scripts/watchdog.py" -o watchdog.py
python3 watchdog.py

Example response

# raw file content (Content-Type matches the file)

Project logs

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.

POST
/api/public/logs/{publicId}

Writes one event, a batch of events, or plain text (one line = one event).

Path parameters

publicIdstring(8) · requiredProject code.

Headers

content-typeapplication/json | text/plain · requiredtext/plain turns every non-empty line into an info event.

Request body (JSON)

messagestring ≤4000Event text. Required.
level"info" | "warn" | "error" | "debug"Defaults to "info". Unknown values fall back to info.
sourcestring ≤120Optional label, e.g. script or device name.
tsISO 8601Time of the event. Defaults to the time of arrival.
eventsarray ≤200Batch form: array of objects with the fields above.

Example request

# 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 @-

Example response

{ "ok": true, "inserted": 2 }
GET
/api/public/logs/{publicId}

Reads the newest events, optionally filtered.

Path parameters

publicIdstring(8) · requiredProject code.
limitinteger 1-1000Number of events. Defaults to 200.
levelstringReturn only events with this level.
sinceISO 8601Only events newer than this timestamp.
qstringSubstring match on the message.
format"json" | "text"text returns a plain-text tail.

Example request

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"

Example response

{
  "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" }
  ]
}

Device status

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.

POST
/api/public/status/device/{deviceToken}

Stores one status report for the device.

Path parameters

deviceTokenstring · requiredDevice token.

Headers

content-typeapplication/json · required

Request body (JSON)

statusstringOne of the allowed statuses above.
projectstring(8)Optional project code the report belongs to.
messagestring ≤2000Optional detail, e.g. an error text.
app_versionstring ≤64Version running on the device.

Example request

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"}'

Example response

{
  "ok": true,
  "id": 128,
  "device": "pi-greenhouse",
  "status": "running",
  "reported_at": "2026-08-15T12:04:11.221Z"
}
GET
/api/public/status/device/{deviceToken}

Returns the most recent status reports for one device.

Path parameters

deviceTokenstring · requiredDevice token.
?limitinteger (query)Default 50, max 500.

Example request

curl -s "https://freymitwo.lovable.app/api/public/status/device/<DEVICE_TOKEN>?limit=10"

Example response

{
  "device": "pi-greenhouse",
  "count": 1,
  "events": [
    { "id": 128, "status": "running", "message": "", "app_version": "4",
      "reported_at": "2026-08-15T12:04:11.221Z", "project_id": "..." }
  ]
}
GET
/api/public/status/project/{publicId}

Returns the most recent status reports linked to one project.

Path parameters

publicIdstring(8) · requiredProject code.
?limitinteger (query)Default 50, max 500.

Example request

curl -s "https://freymitwo.lovable.app/api/public/status/project/<PROJECT_CODE>?limit=10"

Example response

{ "project": "<PROJECT_CODE>", "count": 1, "events": [ /* ... */ ] }

Limits and retention

Status codes

200OKRequest processed.
400Bad requestMalformed JSON or body that fails validation.
401UnauthorizedReserved; no endpoint currently requires authentication.
404Not foundUnknown project code, run id or no release yet.
413Payload too largeMore than 500 log lines in one batch.
500Server errorWrite to the database failed; retry later.

Python helper

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")