Copy the env template
Create a private .env file from the sanitized example.
Copy-Item .env.example .env
cp .env.example .env
Sanitized install and API guide
A documentation-first build for weather, time, Docker status, and guarded email delivery. This page is read-only on purpose: it explains each command, each endpoint, and the UI integration path without exposing secrets.
Install
Use the safe template, install packages, start the app, then open the docs page at http://localhost:3000/.
Create a private .env file from the sanitized example.
Copy-Item .env.example .env
cp .env.example .env
This downloads the packages defined in package.json.
npm install
Launch the API and the docs UI on your configured port.
npm start
Config
Only placeholders belong in docs. Put real values in .env or your deployment secret manager.
| Variable | Required | Example | Purpose |
|---|---|---|---|
PORT |
No | 3000 |
Express server port. |
LOG_LEVEL |
No | info |
Pino logging level. |
WEATHER_API_KEY |
Yes for live weather | your_weather_api_key |
Authenticates requests to WeatherAPI. |
WEATHER_LOCATIONS |
No | Portland, ME|Wallkill, NY |
Default watchlist loaded at startup. |
REFRESH_INTERVAL_MS |
No | 600000 |
Weather refresh cadence in milliseconds. |
WEATHER_LOCATIONS_FILE |
No | ./data/weather-locations.json |
Persisted watchlist file path. |
SEND_EMAIL_AUTH |
Yes for email | your_email_token |
Auth token accepted by x-auth or Authorization: Bearer. |
SMTP_HOST |
Yes for email | smtp.example.com |
SMTP hostname used during startup verification and sending. |
SMTP_PORT |
Yes for email | 587 |
SMTP port. |
SMTP_SECURE |
Yes for email | false |
true for implicit TLS, false for standard SMTP or STARTTLS. |
SMTP_USER |
Yes for email | [email protected] |
SMTP username. |
SMTP_PASS |
Yes for email | your_smtp_password |
SMTP password. |
SMTP_FROM |
Yes for email | [email protected] |
Default sender address. |
SMTP_REQUIRE_TLS |
No | true |
Require TLS when the server supports it. |
SMTP_ALLOW_SELF_SIGNED |
No | false |
Non-production escape hatch for self-signed certificates. |
DOCKER_SOCKET |
No | \\\\.\\pipe\\docker_engine |
Socket or named pipe path used by Docker container listing. |
401. Replace the key in .env and restart the app.
Commands
Each command below is safe to copy. Replace only the placeholder values in your private environment file.
Use these first to confirm the service boots and the docs page is available.
curl http://localhost:3000/time curl http://localhost:3000/weather/watchlist
Invoke-RestMethod -Uri "http://localhost:3000/time" Invoke-RestMethod -Uri "http://localhost:3000/weather/watchlist"
Build a generic local image and mount a persistent data volume.
docker build -t omni-api:0.0.33 .
docker run -d \ --name omni-api \ -p 3000:3000 \ --env-file .env \ -v omni-api-data:/usr/src/app/data \ -v /var/run/docker.sock:/var/run/docker.sock \ -e DOCKER_SOCKET=/var/run/docker.sock \ --restart unless-stopped \ omni-api:0.0.33
Reference
Every endpoint below includes purpose, payload rules, response shape, and copy-ready examples for command line and UI code.
No auth required. Useful for health checks and “last updated” UI badges.
curl http://localhost:3000/time
Invoke-RestMethod -Uri "http://localhost:3000/time"
const response = await fetch("/time");
const data = await response.json();
document.querySelector("#server-time").textContent = data.locale;
{
"iso": "2026-05-12T18:52:30.847Z",
"locale": "5/12/2026, 2:52:30 PM",
"timestamp": 1778611950847
}
No auth required. This is the most detailed weather response and is best suited for internal dashboards.
curl http://localhost:3000/weather
const response = await fetch("/weather");
const forecasts = await response.json();
No auth required. Returns lighter data than /weather, making it a better fit for public-facing cards.
curl http://localhost:3000/weather/all
const response = await fetch("/weather/all");
const summary = await response.json();
summary.forEach((item) => {
console.log(item.location, item.data.current.temp_f);
});
[
{
"id": 0,
"location": "Portland, ME",
"data": {
"location": { "name": "Portland" },
"current": { "temp_f": 58.6 }
}
}
]
No auth required. Use this to populate a UI list before loading individual weather records.
curl http://localhost:3000/weather/watchlist
Invoke-RestMethod -Uri "http://localhost:3000/weather/watchlist"
const response = await fetch("/weather/watchlist");
const watchlist = await response.json();
renderWatchlist(watchlist.locations);
{
"refreshIntervalMs": 600000,
"freshDebounceMs": 5000,
"locations": [
{ "id": 0, "location": "Portland, ME" },
{ "id": 1, "location": "Wallkill, NY" }
]
}
No auth required. Request body must include location.
curl -X POST http://localhost:3000/weather/watchlist \
-H "Content-Type: application/json" \
-d "{\"location\":\"Hollis Center, ME\"}"
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:3000/weather/watchlist" `
-ContentType "application/json" `
-Body '{"location":"Hollis Center, ME"}'
await fetch("/weather/watchlist", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ location: "Hollis Center, ME" })
});
No auth required. Replace :id with a numeric watchlist ID.
curl http://localhost:3000/weather/0
const response = await fetch("/weather/0");
const result = await response.json();
No auth required. Use this when you need to bypass the short debounce window.
curl http://localhost:3000/weather/fresh/0
const response = await fetch("/weather/fresh/0");
const freshData = await response.json();
No auth required. This endpoint works only when the Docker socket or named pipe is accessible to the app.
curl http://localhost:3000/docker/liststate
const response = await fetch("/docker/liststate");
const dockerState = await response.json();
{
"containers": [
{
"id": "abc123",
"image": "nginx:latest",
"state": "running",
"status": "Up 3 hours"
}
]
}
Requires x-auth or Authorization: Bearer. Canonical request fields are
to, subject, body, and delaySeconds.
Legacy fields Subject, Body, and timedelay are still accepted.
curl -X POST http://localhost:3000/sendemail \
-H "Content-Type: application/json" \
-H "x-auth: your_email_token" \
-d "{\"to\":\"[email protected]\",\"subject\":\"Hello\",\"body\":\"Testing email delivery\",\"delaySeconds\":0}"
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:3000/sendemail" `
-Headers @{ "x-auth" = "your_email_token" } `
-ContentType "application/json" `
-Body '{"to":"[email protected]","subject":"Hello","body":"Testing email delivery","delaySeconds":0}'
await fetch("/sendemail", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-auth": "your_email_token"
},
body: JSON.stringify({
to: "[email protected]",
subject: "Hello",
body: "<strong>Testing email delivery</strong>",
delaySeconds: 0
})
});
{
"status": "sent",
"messageId": "<[email protected]>",
"accepted": ["[email protected]"],
"rejected": []
}
503 with {"status":"unavailable","error":"Email service unavailable"} instead of using risky silent fallbacks.
Frontend
These examples are ready to drop into a browser-based UI. The public docs page stays read-only, but your own app can call the JSON endpoints directly.
Refresh a “last seen” or “server time” label.
async function loadServerTime() {
const response = await fetch("/time");
const data = await response.json();
document.querySelector("#server-time").textContent = data.locale;
}
Load the watchlist first, then decide which detailed endpoint to call next.
async function loadWatchlist() {
const response = await fetch("/weather/watchlist");
const data = await response.json();
return data.locations;
}
Send canonical fields from a UI form while keeping the auth token outside the public page.
async function sendEmail(emailToken, formValues) {
const response = await fetch("/sendemail", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-auth": emailToken
},
body: JSON.stringify({
to: formValues.to,
subject: formValues.subject,
body: formValues.body,
delaySeconds: formValues.delaySeconds ?? 0
})
});
if (!response.ok) {
throw new Error("Email request failed");
}
return response.json();
}
Support
The app stays up even when external integrations are unavailable, but the failing endpoint will tell you why.
Your WeatherAPI key is invalid or expired. Replace WEATHER_API_KEY in .env and restart the process.
SMTP settings are missing or startup verification failed. Check every required SMTP variable and confirm the certificate chain is valid.
This workspace does not include .git metadata, so past secret exposure must be cleaned in the real source repository, not only in this folder.