DYNVOLT · developers

Quickstart

Create a token and read live plant data in a few minutes.

1. Create an API key

API keys are created self-service by client admins in the owner platform:

  1. Sign in at owner.dynvolt.com.
  2. Go to Settings → Admin → API access.
  3. Create a key, pick the scopes it needs, and copy it.

The screen only offers scopes your plan includes. Grant the fewest a given integration needs — the 17 scopes range from read-only telemetry to the two control scopes (curtailment:write, bess:write) that command the plant.

The full key is shown once, at creation. Store it in a secrets manager immediately — you cannot retrieve it again, only rotate it.

Keys look like this:

dvk_live_a1b2c3d4e5f6_9f8e7d6c...  (dvk_live_<12 hex>_<64 hex>)

2. Make your first request

List the sites your key can access:

curl https://api.owner.dynvolt.com/v1/sites \
  -H "Authorization: Bearer $DYNVOLT_API_KEY"
import httpx

client = httpx.Client(
    base_url="https://api.owner.dynvolt.com",
    headers={"Authorization": f"Bearer {DYNVOLT_API_KEY}"},
)

sites = client.get("/v1/sites").json()
print(sites["data"])
const res = await fetch('https://api.owner.dynvolt.com/v1/sites', {
  headers: { Authorization: `Bearer ${process.env.DYNVOLT_API_KEY}` },
});
const { data } = await res.json();
console.log(data);

A successful response returns the list envelope used by all list endpoints:

{
  "data": [
    {
      "site_id": "pv-ljubas",
      "name": "PV Ljubas",
      "health_status": "healthy"
    }
  ],
  "meta": { "count": 1 }
}

health_status is one of healthy, degraded, offline, or unknown.

3. Read live plant data

Use a site_id from step 2 to pull a full live snapshot — loggers, inverters, sensors, weather, and predictions in one call (requires the plant:read scope):

curl "https://api.owner.dynvolt.com/v1/sites/pv-ljubas/overview" \
  -H "Authorization: Bearer $DYNVOLT_API_KEY"
overview = client.get("/v1/sites/pv-ljubas/overview").json()
const overview = await fetch(
  'https://api.owner.dynvolt.com/v1/sites/pv-ljubas/overview',
  { headers: { Authorization: `Bearer ${process.env.DYNVOLT_API_KEY}` } },
).then((r) => r.json());

For narrower reads, see /plant/status, /inverters, or /energy/hourly in the reference. The full surface is 108 operations — weather/EMI, efficiency & I-V diagnostics, curtailment, markets, earnings, predictions, full BESS telemetry, alerts, O&M, and reports.

4. Create a work order

Writes need a write scope — here om:write. Pass an Idempotency-Key so a retried request is not applied twice:

curl -X POST "https://api.owner.dynvolt.com/v1/sites/pv-ljubas/work-orders" \
  -H "Authorization: Bearer $DYNVOLT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c9d2e-4b0a-4f3f-8f2f-1a2b3c4d5e6f" \
  -d '{
    "title": "Inspect string L4-07 underperformance",
    "description": "String is 12% below orientation peers for 3 days.",
    "priority": "medium"
  }'
import uuid

wo = client.post(
    "/v1/sites/pv-ljubas/work-orders",
    headers={"Idempotency-Key": str(uuid.uuid4())},
    json={
        "title": "Inspect string L4-07 underperformance",
        "description": "String is 12% below orientation peers for 3 days.",
        "priority": "medium",
    },
).json()
const wo = await fetch(
  'https://api.owner.dynvolt.com/v1/sites/pv-ljubas/work-orders',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.DYNVOLT_API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': crypto.randomUUID(),
    },
    body: JSON.stringify({
      title: 'Inspect string L4-07 underperformance',
      description: 'String is 12% below orientation peers for 3 days.',
      priority: 'medium',
    }),
  },
).then((r) => r.json());

Where to go next

  • Authentication — key format, rotation, storage advice
  • Scopes & modules — all 17 scopes and which are control scopes
  • Curtailment — schedule / immediate / release, and the grid-permit invariant
  • BESS — telemetry plus the dispatch-approval workflow
  • Errors — what a 403 forbidden_scope or 409 conflict_safe_mode means
  • Rate limits — 120 req/min, and how to back off
  • API reference — all 108 operations, grouped by area