Projects & source
Discover and read projects.
Enumerate your organization's projects, read one project's metadata, and pull its source in whichever representation you need.
List & get
GET /projects needs only a valid key (no extra scope) — even an ai_explain-only key can discover which projects exist. Results are filtered to the key's project scope and paginated with an opaque cursor.
curl -s "https://app.plcs.ai/api/v1/projects?limit=50" \
-H "Authorization: Bearer $PLCS_API_KEY"page = client.list_projects(limit=50)
for p in page.projects:
print(p.project_id, p.name, p.vendor)
detail = client.get_project(page.projects[0].project_id)
print(detail.analysis_status) # queued | running | complete | error | Nonevar page = await client.ListProjectsAsync(limit: 50);
foreach (var p in page.Projects)
Console.WriteLine($"{p.ProjectId} {p.Name} {p.Vendor}");
var detail = await client.GetProjectAsync(page.Projects[0].ProjectId);
Console.WriteLine(detail.AnalysisStatus);Read source
GET /projects/{id}/source requires code_read and serves the project two ways — and nothing in between:
| Format | Returns |
|---|---|
parsed (default) | The vendor-neutral parsed model as JSON (programs, routines, tags, dataTypes…), served from storage. Gzipped on the wire — advertise Accept-Encoding: gzip and you get it verbatim; the SDKs do. |
raw | The original uploaded file (L5X XML, Siemens ZIP, or CODESYS .export) as a binary download. |
There are no per-routine or aggregated-text reads. To ask a question about the project, use interpret rather than pulling the model and reasoning over it yourself — that is the capability the API is for.
No size ceiling. The parsed model is handed over from storage rather than assembled in the response, so a real project — a few MB into the tens of MB — comes back whole. Hold it deliberately rather than logging or echoing it.
src = client.get_source("prj_…") # the parsed model
print(src.parsed["controller"]["name"])
raw_bytes = client.download_source("prj_…") # original L5X / ZIP / .export bytesvar src = await client.GetSourceAsync("prj_…");
Console.WriteLine(src.Parsed);
byte[] rawBytes = await client.DownloadSourceAsync("prj_…");Live HMI values
With hmi_view, GET /projects/{id}/hmi/values returns the latest tag snapshot pushed by a Desktop Companion App (DCA) session, and /hmi/history?tag=… returns recent history for one tag. These return data only while a DCA is serving the project; with no live session you get a normal 200 with live: false and an empty tags map (not an error).
To ask for tags the operator never started streaming, POST /projects/{id}/hmi/request-tags records the names for the DCA to read on its next poll and returns { project_id, live, requested }. It needs hmi_edit, and it is a request signal rather than a read — call /hmi/values a few seconds later for the values. The list is de-duped and not truncated, so a whole-project ask is one call.
Reads need no Idempotency-Key. Listing is scoped to the key's projects; a request for a project outside the scope returns 403 project_scope_forbidden.