API Reference

Base URL

https://api.vbatpower.com

All endpoints are prefixed with /api/v1. Authentication is via session cookies (for browser clients) or API keys (for MCU/server clients). The API is free with no usage limits beyond the abuse-protection rate limits described below.

Authentication

POST /api/v1/auth/register

Create a new account with email and password (minimum 8 characters, maximum 72).

{"email": "user@example.com", "password": "securePassword123"}

Returns 201 with user object and sets session cookie. Returns 409 if email already exists. Rate limited to 5 requests / 15 min per IP in production.

POST /api/v1/auth/login

Sign in with email and password.

{"email": "user@example.com", "password": "securePassword123"}

Returns 200 with user object and sets session cookie. Returns 401 on wrong credentials. Rate limited to 10 requests / 15 min per IP in production.

GET /api/v1/auth/me

Get the current authenticated user. Requires session cookie.

Returns 200 with user object containing id, email, name, role fields, or 401 if not authenticated.

POST /api/v1/auth/logout

End the current session. Clears the session cookie and deletes the session row.

POST /api/v1/auth/logout-all

Revoke every session for the current user (sign out on all devices). Requires session cookie. Returns 200 with {"status": "ok"}, or 401 if not authenticated.

GET /api/v1/auth/providers

List the OAuth providers currently enabled on the server. Public β€” no authentication required. Returns {"providers": ["google", "github"]} (the array contains only providers configured at deploy time).

GET /api/v1/auth/oauth/:provider

Redirect to OAuth provider login. Supported providers: google, microsoft, github. After consent the provider returns to /api/v1/auth/oauth/:provider/callback, which sets the session cookie and redirects back to the app.

Projects

All project endpoints require authentication (session cookie). Project bodies may be up to 2 MB.

GET /api/v1/projects

List all projects for the authenticated user, ordered by last update. Returns id, name, description, tags, and timestamps (not the full data blob).

POST /api/v1/projects

Create a new project. name is required (max 255 chars); description (max 4096 chars), tags (array of up to 50 strings, each up to 64 chars), and data are optional. Returns 201.

{"name": "Sensor Node", "data": {"batteries": [...]}}
GET /api/v1/projects/:id

Get a project by ID (returns full data including batteries). Returns 404 if not found or not owned by user.

PUT /api/v1/projects/:id

Update a project. Only provided fields are updated (PATCH semantics β€” omitted fields keep their current value). Returns 404 if the project is not owned by the user.

DELETE /api/v1/projects/:id

Delete a project and all associated data. Returns 204 on success.

Project data schema

The data field holds the project model: an object with a batteries array. Each battery has a numeric id (its depth-1 hierarchical index) and a components array; each component has an id and a states array; each state has an id plus its operating-mode fields. The id values map directly onto the dot-notation telemetry IDs (battery 0 β†’ component 0.0 β†’ state 0.0.0).

{
  "name": "Sensor Node",
  "description": "ESP32 + INA219",
  "tags": ["esp32", "lipo"],
  "data": {
    "batteries": [
      {
        "id": 0,
        "name": "18650 cell",
        "voltageMin": 3.0,
        "voltageMax": 4.2,
        "current": 2.5,
        "currentUnit": "Ah",
        "components": [
          {
            "id": 0,
            "name": "ESP32",
            "states": [
              {"id": 0, "name": "Deep Sleep", "alwaysOn": true, "current": 10, "currentUnit": "u"},
              {"id": 1, "name": "Active", "current": 80, "currentUnit": "m", "interval": 60, "intervalUnit": "s", "execTime": 5, "execTimeUnit": "s"}
            ]
          }
        ]
      }
    ]
  }
}

Common state fields: name, alwaysOn (boolean), current + currentUnit, interval + intervalUnit, execTime + execTimeUnit, and runOnce (boolean). The data object is stored as-is, so any fields produced by the app are round-tripped.

Telemetry

POST /api/v1/telemetry

Upload telemetry records. Requires API key as Authorization: Bearer <key> header. Up to 10,000 records per request; the key determines the target project. Rate limited to 60 requests / minute per project (returns 429 when exceeded). Returns 201 with {"status": "ok", "inserted": N}.

{"records": [
  {"id": "0", "type": "v", "value": 3.28, "unit": "V", "nonce": 1, "runid": 1},
  {"id": "0.0.0", "type": "t", "value": 1523, "unit": "ms", "nonce": 1, "runid": 1},
  {"id": "0.0.0", "type": "v", "value": 12.5, "unit": "mA", "nonce": 1, "runid": 1}
]}
GET /api/v1/telemetry/:projectId

Retrieve telemetry records for a project. Requires API key as Authorization: Bearer <key>; the key must be scoped to the requested project (otherwise 403).

Results are paginated by id cursor. Query params: limit (1–1000, default 1000) and cursor (return rows with id greater than this value, default 0). The response includes next_cursor β€” pass it back as cursor to fetch the next page; it is null when no rows were returned.

{
  "project_id": 1,
  "records": [
    {"id": 42, "record_id": "0", "record_type": "v", "value": 3.28, "unit": "V", "nonce": 1, "runid": 1, "received_at": "2026-01-01T12:00:00.000Z"}
  ],
  "next_cursor": 42
}

Formula History

All formula-history endpoints require authentication (session cookie).

POST /api/v1/formulas/history

Save a formula calculation to history. formula_id and solve_for are required. Returns 201.

{"formula_id": "ohms-law", "solve_for": "V", "inputs": {"I": 0.5, "R": 100}, "result": 50}
GET /api/v1/formulas/history?limit=50

Get recent formula calculations, newest first. limit is clamped to 1–500 (default 50).

DELETE /api/v1/formulas/history/:id

Delete a single history entry by ID. Returns 200 with {"status": "ok"}, or 404 if the entry is not found or not owned by the user.

DELETE /api/v1/formulas/history

Clear the entire formula history for the current user. Returns 200 with {"status": "ok"}.

API Keys

All API-key management endpoints require authentication (session cookie). Keys are used by MCUs and scripts to upload telemetry without a browser session.

POST /api/v1/keys

Create an API key scoped to a project. project_id is required; name is optional (max 128 chars). Up to 50 active keys per user.

{"project_id": 1, "name": "ESP32 Sensor"}

Returns 201 with the key string (prefixed vbat_). Store it securely β€” it cannot be retrieved again.

GET /api/v1/keys

List the user's API keys, newest first. Returns id, project_id, key prefix, name, created timestamp, and revoked flag β€” never the full key.

DELETE /api/v1/keys/:id

Revoke an API key by ID. The key stops working immediately. Returns 200 with {"status": "ok"}, or 404 if the key is not found or already revoked.

Rate Limits

VBatPower is free with no usage quotas. The only limits are abuse-protection rate limits, enforced per IP (auth) or per project (telemetry) in production:

  • POST /api/v1/auth/register β€” 5 requests per 15 minutes per IP
  • POST /api/v1/auth/login β€” 10 requests per 15 minutes per IP
  • POST /api/v1/telemetry β€” 60 requests per minute per project

Exceeding a limit returns 429 with body {"error": "Too many requests"}. Request bodies are also capped: 64 KB for auth, keys, and formula-history routes; 2 MB for projects and telemetry. Oversized bodies return 413 with {"error": "Payload too large"}.

Error Responses

All errors return JSON with an error field describing the problem:

{"error": "Not authenticated"}

HTTP status codes used by the API:

  • 400 β€” bad request (invalid JSON, missing or malformed field)
  • 401 β€” not authenticated (missing/expired session or invalid API key)
  • 403 β€” forbidden (API key not scoped to the requested project)
  • 404 β€” not found (or not owned by the authenticated user)
  • 409 β€” conflict (email already registered)
  • 413 β€” payload too large
  • 429 β€” too many requests (rate limit exceeded)
  • 500 β€” internal error

End-to-End MCU Walkthrough

This ties the endpoints together for the typical hardware flow: generate a key, create a project, push telemetry, and read it back. All requests use the base URL https://api.vbatpower.com.

1. Create a project (browser session)

Sign in to the app, or call the API with your session cookie:

curl -X POST https://api.vbatpower.com/api/v1/projects \
  -H "Content-Type: application/json" \
  --cookie "session=YOUR_SESSION" \
  -d '{"name":"Sensor Node","data":{"batteries":[{"id":0,"components":[{"id":0,"states":[{"id":0}]}]}]}}'
# -> 201 {"project": {"id": 1, ...}}

2. Generate an API key for that project

curl -X POST https://api.vbatpower.com/api/v1/keys \
  -H "Content-Type: application/json" \
  --cookie "session=YOUR_SESSION" \
  -d '{"project_id":1,"name":"ESP32 Sensor"}'
# -> 201 {"key": "vbat_xxxxxxxx...", "project_id": 1}
# Copy the key now β€” it is shown only once.

3. POST telemetry from the device (Bearer key)

No session or cookie needed β€” the key authenticates the device and selects the project. See MCU Integration for Arduino/ESP32/STM32 code that produces this request.

curl -X POST https://api.vbatpower.com/api/v1/telemetry \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer vbat_xxxxxxxx..." \
  -d '{"records":[
    {"id":"0","type":"v","value":3.28,"unit":"V","nonce":1,"runid":1},
    {"id":"0.0.0","type":"t","value":1523,"unit":"ms","nonce":1,"runid":1},
    {"id":"0.0.0","type":"v","value":12.5,"unit":"mA","nonce":1,"runid":1},
    {"id":"0.0.0","type":"n","value":0,"unit":"ms","nonce":1,"runid":1}
  ]}'
# -> 201 {"status": "ok", "inserted": 4}

4. Verify the data landed

Read it back with the same key, or open the project's Graphs page in the app:

curl https://api.vbatpower.com/api/v1/telemetry/1?limit=1000 \
  -H "Authorization: Bearer vbat_xxxxxxxx..."
# -> 200 {"project_id": 1, "records": [...], "next_cursor": 42}
# Page through more rows by passing ?cursor=42 on the next call.