> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenADR 3.0 (demand response)

> Client-mode REST API connector for OpenADR 3.0 demand response: get an OAuth token from a VTN, read programs and events (prices, load shed, capacity limits, GHG signals), send reports, manage webhook subscriptions, register VENs and resources, and publish events as business logic

Technical Preview

## Overview

**OpenADR 3** is the OpenADR Alliance standard for demand response and dynamic pricing over a REST API. A utility or aggregator runs a **VTN** (Virtual Top Node). Sites and devices connect to it as **VENs** (Virtual End Nodes): they read **programs** and **events** (price signals, load-shed requests, import / export capacity limits, GHG signals) and send **reports** (measured usage, baselines, status) back. A **business logic** (BL) client on the utility side creates the events.

The **OpenAdr3Vtn** connector maps 26 MQTT topics onto the OpenADR 3.0.1 API: one token request, 12 read events and 13 write events. With it, a Coreflux broker can act as the VEN for a plant or building: it picks up DR events on MQTT, hands them to the controls that shed or shift load, and reports the measured response to the utility. Each event publishes the VTN reply on `<topic>/response`.

| Event | Topic | Request | Use |
| - | - | - | - |
| `FetchToken` | `energy/openadr/auth/token` | `POST /auth/token` | OAuth 2.0 client credentials -> access\_token (Bearer), expires\_in; form body grant\_type=client\_credentials\&client\_id=\&client\_secret=\&scope= |
| `ListPrograms` | `energy/openadr/program/list` | `GET /programs?{query}` | DR programs the client may read (programName, intervalPeriod, payloadDescriptors, targets); query targetType=\&targetValues=\&skip=\&limit= |
| `GetProgram` | `energy/openadr/program/get` | `GET /programs/{id}` | One program by id |
| `ListEvents` | `energy/openadr/event/list` | `GET /events?{query}` | DR events: intervals with payloads (PRICE, SIMPLE, IMPORT\_CAPACITY\_LIMIT, GHG ...), priority, targets; query programID=\&targetType=\&targetValues=\&skip=\&limit= |
| `GetEvent` | `energy/openadr/event/get` | `GET /events/{id}` | One event by id |
| `CreateEvent` | `energy/openadr/event/create` | `POST /events` | WRITE (business logic, write\_events): publish a DR event to the targeted VENs; body \{programID, intervals, ...} |
| `UpdateEvent` | `energy/openadr/event/update` | `PUT /events/{id}` | WRITE (business logic): replace an event (full object) |
| `DeleteEvent` | `energy/openadr/event/delete` | `DELETE /events/{id}` | WRITE (business logic): delete (cancel) an event |
| `ListReports` | `energy/openadr/report/list` | `GET /reports?{query}` | Reports; query programID=\&eventID=\&clientName=\&skip=\&limit= |
| `GetReport` | `energy/openadr/report/get` | `GET /reports/{id}` | One report by id |
| `CreateReport` | `energy/openadr/report/create` | `POST /reports` | WRITE (VEN, write\_reports): telemetry back to the VTN; body \{programID, eventID, clientName, resources\[\{resourceName, intervals}]} |
| `UpdateReport` | `energy/openadr/report/update` | `PUT /reports/{id}` | WRITE (VEN): replace a report (full object) |
| `DeleteReport` | `energy/openadr/report/delete` | `DELETE /reports/{id}` | WRITE (VEN): delete a report |
| `ListSubscriptions` | `energy/openadr/subscription/list` | `GET /subscriptions?{query}` | Subscriptions; query programID=\&clientName=\&targetType=\&targetValues=\&objects=\&skip=\&limit= |
| `GetSubscription` | `energy/openadr/subscription/get` | `GET /subscriptions/{id}` | One subscription by id |
| `CreateSubscription` | `energy/openadr/subscription/create` | `POST /subscriptions` | WRITE: register a webhook; body \{clientName, programID, objectOperations\[\{objects, operations, callbackUrl, bearerToken}]} |
| `UpdateSubscription` | `energy/openadr/subscription/update` | `PUT /subscriptions/{id}` | WRITE: replace a subscription (full object) |
| `DeleteSubscription` | `energy/openadr/subscription/delete` | `DELETE /subscriptions/{id}` | WRITE: delete a subscription |
| `ListVens` | `energy/openadr/ven/list` | `GET /vens?{query}` | VENs visible to the client (venName, attributes, targets); query venName=\&targetType=\&targetValues=\&skip=\&limit= |
| `GetVen` | `energy/openadr/ven/get` | `GET /vens/{id}` | One VEN by id |
| `CreateVen` | `energy/openadr/ven/create` | `POST /vens` | WRITE (write\_vens): register a VEN; body \{venName, attributes, targets} |
| `UpdateVen` | `energy/openadr/ven/update` | `PUT /vens/{id}` | WRITE: replace a VEN (full object) |
| `ListResources` | `energy/openadr/resource/list` | `GET /vens/{ven_id}/resources?{query}` | Resources (meters, batteries, chargers) of a VEN; query resourceName=\&targetType=\&targetValues=\&skip=\&limit= |
| `GetResource` | `energy/openadr/resource/get` | `GET /vens/{ven_id}/resources/{id}` | One resource of a VEN |
| `CreateResource` | `energy/openadr/resource/create` | `POST /vens/{ven_id}/resources` | WRITE (write\_vens): add a resource to a VEN; body \{resourceName, venID, attributes, targets} |
| `UpdateResource` | `energy/openadr/resource/update` | `PUT /vens/{ven_id}/resources/{id}` | WRITE: replace a resource (full object) |

All endpoints carry the base path `/openadr3/3.0.1` (left out in the table).

> [!WARNING]
>
> **Write events change VTN state.** `CreateEvent`, `UpdateEvent` and `DeleteEvent` need the business-logic role (`write_events`): a new event is a demand-response instruction to every targeted VEN. Reports, subscriptions and VEN / resource registration are VEN writes. Restrict who can publish to these topics with a rule. This connector leaves out program administration and VEN / resource deletion.

## Prerequisites

* Access to a **VTN**: the URL from your utility, aggregator or DR program, or your own VTN (for example the open-source [openleadr-rs](https://github.com/OpenLEADR/openleadr-rs) VTN).
* **Client credentials** (`client_id`, `client_secret`) for a VEN client, and a business-logic client if you create events. The VTN operator issues them.
* The VTN **base path**. The OpenADR 3.0.1 specification uses `/openadr3/3.0.1`. Some VTNs serve the API at a different path or at the root. Change the endpoints if yours does.
* Outbound HTTPS from the broker to the VTN.

## Authentication

OpenADR 3 uses **OAuth 2.0 client credentials**. `FetchToken` posts a form body to `auth/token` and gets back `{"access_token": "...", "token_type": "Bearer", "expires_in": 3600}`. Every other event sends `Authorization: {value.json.authorization}` with the value `Bearer <access_token>`.

| Secret | Value |
| - | - |
| `OPENADR_TOKEN_REQUEST` | `grant_type=client_credentials&client_id=<id>&client_secret=<secret>` (add `&scope=read_all` when the VTN wants a scope) |
| `OPENADR_AUTHORIZATION` | `Bearer <access_token>`, refreshed before `expires_in` runs out |

The specification calls `auth/token` a convenience endpoint that a commercial VTN may leave out. In that case, get the token from the utility's authorization server and store it in `OPENADR_AUTHORIZATION`.

Scopes: `read_all` (VENs and BL), `write_programs` and `write_events` (BL), `write_reports` (VENs), `write_subscriptions` and `write_vens` (VENs and BL).

| Response | Meaning |
| - | - |
| `400` `{"error": "invalid_client"}` | Wrong client id or secret on `auth/token` |
| `403` problem JSON | Missing or invalid token, or the token's scope does not allow the call |
| `404` problem JSON | Unknown object id |
| `409` problem JSON | The object already exists (for example a duplicate `venName`) |

## Configuration

| Setting | Value |
| - | - |
| `BASE_ADDRESS` | Your VTN host, such as `https://vtn.utility.example` (placeholder `https://vtn.example.com`) |
| `USE_SSL` | `"true"` |
| `DEFAULT_CONTENT_TYPE` | `application/json` (`auth/token` sends `application/x-www-form-urlencoded`) |
| Payload fields | `authorization`, `id` (object id), `ven_id` (resource events), `query` (pre-encoded query string, `""` for none), `body` (JSON object; a form string for `auth/token`) |

Query parameters (3.0.1):

| Event | Parameters |
| - | - |
| `ListPrograms` | `targetType`, `targetValues`, `skip`, `limit` |
| `ListEvents` | `programID`, `targetType`, `targetValues`, `skip`, `limit` |
| `ListReports` | `programID`, `eventID`, `clientName`, `skip`, `limit` |
| `ListSubscriptions` | `programID`, `clientName`, `targetType`, `targetValues`, `objects`, `skip`, `limit` |
| `ListVens` | `venName`, `targetType`, `targetValues`, `skip`, `limit` |
| `ListResources` | `resourceName`, `targetType`, `targetValues`, `skip`, `limit` |

An event carries `intervals`, and each interval has `payloads` such as `{"type": "PRICE", "values": [0.17]}` or `{"type": "IMPORT_CAPACITY_LIMIT", "values": [50]}`. `intervalPeriod` sets the start and duration (ISO 8601, for example `PT1H`).

## Basic Syntax

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
# Coreflux REST_API template — OpenADR 3.0 (3.0.1) demand response, VEN / business-logic client of a VTN:
#   get an OAuth 2.0 client-credentials token, read DR programs and events (price, load shed, GHG and other
#   signals), read, create, update and delete reports (telemetry / baselines back to the utility), manage
#   subscriptions (webhook callbacks), and read / register VENs and their resources; plus (marked WRITE,
#   business-logic role only) create, update and delete events.
# Hugo catalog #180, Tier 2 (energy). Spec: OpenADR Alliance OpenAPI "OpenADR 3 API" 3.0.1 (public copy
#   github.com/grid-coordination/openadr3-specification, upstream oadr3-org/specification a35682b; Apache 2.0),
#   curated slim in openapi-cache/openadr-3-0.json. Credentials are placeholders only.
# Docs: https://docs.coreflux.org/latest/lot-language/routes/rest-api-apps/openadr-3-0
#
# VTN: every utility / aggregator runs its own VTN. BASE_ADDRESS is a placeholder: set it to your VTN host and
#   change /openadr3/3.0.1 in the endpoints if your VTN uses another base path (the spec server is
#   http://localhost:8081/openadr3/3.0.1; openleadr-rs serves the API at its root).
# Auth: Authorization: {value.json.authorization} = "Bearer <access_token>" on every call except auth/token.
#   Tokens come from OAuth 2.0 client credentials. The VTN may host POST {base}/auth/token (FetchToken, form
#   body grant_type=client_credentials&client_id=...&client_secret=...&scope=...) or point to an external
#   authorization server (then fetch the token there). Scopes: read_all, write_programs, write_events
#   (business logic), write_reports (VEN), write_subscriptions, write_vens. Keep client id / secret in Hub secrets
#   (suggested OPENADR_CLIENT_ID, OPENADR_CLIENT_SECRET) and the current token in OPENADR_AUTHORIZATION.
#   Errors: 400 / 403 / 404 / 409 / 500 as application/problem+json {type, title, status, detail}.
# Queries: list events with ?{value.json.query} take a pre-encoded query string ("" for none):
#     programs        targetType=&targetValues=&skip=&limit=
#     events          programID=&targetType=&targetValues=&skip=&limit=
#     reports         programID=&eventID=&clientName=&skip=&limit=
#     subscriptions   programID=&clientName=&targetType=&targetValues=&objects=&skip=&limit=
#     vens            venName=&targetType=&targetValues=&skip=&limit=
#     resources       resourceName=&targetType=&targetValues=&skip=&limit=
# Writes (marked WRITE below): reports, subscriptions, VEN / resource registration (VEN role) and events
#   (business-logic role: a new event is a demand-response instruction to every targeted VEN). Not packaged:
#   program create / update / delete, VEN and resource delete.
# Webhooks: POST subscription/create with objectOperations[].callbackUrl pointing to a Coreflux REST_API server
#   route (ENABLE_SERVER) to get pushed notifications; the VTN POSTs {objectType, operation, object} to it.
#
# Payload fields: authorization, id (object id), ven_id (resources), query, body (JSON object; form string for
#   auth/token)
#     FetchToken     body = "grant_type=client_credentials&client_id=ven_client_99&client_secret=<secret>"
#     ListEvents     query = "programID=0&limit=50"
#     CreateReport   body = {"programID": "0", "eventID": "0", "clientName": "VEN-999", "resources": [...]}

DEFINE ROUTE OpenAdr3Vtn WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS "https://vtn.example.com"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH REQUEST_TIMEOUT '30'
        WITH DEFAULT_CONTENT_TYPE "application/json"
        # Smoke: FetchToken -> ListPrograms -> ListEvents?programID= -> CreateReport -> ListReports -> DeleteReport.
    ADD EVENT FetchToken
        WITH SOURCE_TOPIC "energy/openadr/auth/token"
        WITH DESTINATION_TOPIC "energy/openadr/auth/token/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/openadr3/3.0.1/auth/token"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/x-www-form-urlencoded"
        WITH BODY "{value.json.body}"
    ADD EVENT ListPrograms
        WITH SOURCE_TOPIC "energy/openadr/program/list"
        WITH DESTINATION_TOPIC "energy/openadr/program/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/openadr3/3.0.1/programs?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    ADD EVENT GetProgram
        WITH SOURCE_TOPIC "energy/openadr/program/get"
        WITH DESTINATION_TOPIC "energy/openadr/program/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/openadr3/3.0.1/programs/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    ADD EVENT ListEvents
        WITH SOURCE_TOPIC "energy/openadr/event/list"
        WITH DESTINATION_TOPIC "energy/openadr/event/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/openadr3/3.0.1/events?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    ADD EVENT GetEvent
        WITH SOURCE_TOPIC "energy/openadr/event/get"
        WITH DESTINATION_TOPIC "energy/openadr/event/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/openadr3/3.0.1/events/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    # WRITE: CreateEvent changes VTN state. Restrict who can publish to energy/openadr/event/create.
    ADD EVENT CreateEvent
        WITH SOURCE_TOPIC "energy/openadr/event/create"
        WITH DESTINATION_TOPIC "energy/openadr/event/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/openadr3/3.0.1/events"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: UpdateEvent changes VTN state. Restrict who can publish to energy/openadr/event/update.
    ADD EVENT UpdateEvent
        WITH SOURCE_TOPIC "energy/openadr/event/update"
        WITH DESTINATION_TOPIC "energy/openadr/event/update/response"
        WITH METHOD "PUT"
        WITH ENDPOINT "/openadr3/3.0.1/events/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: DeleteEvent changes VTN state. Restrict who can publish to energy/openadr/event/delete.
    ADD EVENT DeleteEvent
        WITH SOURCE_TOPIC "energy/openadr/event/delete"
        WITH DESTINATION_TOPIC "energy/openadr/event/delete/response"
        WITH METHOD "DELETE"
        WITH ENDPOINT "/openadr3/3.0.1/events/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    ADD EVENT ListReports
        WITH SOURCE_TOPIC "energy/openadr/report/list"
        WITH DESTINATION_TOPIC "energy/openadr/report/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/openadr3/3.0.1/reports?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    ADD EVENT GetReport
        WITH SOURCE_TOPIC "energy/openadr/report/get"
        WITH DESTINATION_TOPIC "energy/openadr/report/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/openadr3/3.0.1/reports/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    # WRITE: CreateReport changes VTN state. Restrict who can publish to energy/openadr/report/create.
    ADD EVENT CreateReport
        WITH SOURCE_TOPIC "energy/openadr/report/create"
        WITH DESTINATION_TOPIC "energy/openadr/report/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/openadr3/3.0.1/reports"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: UpdateReport changes VTN state. Restrict who can publish to energy/openadr/report/update.
    ADD EVENT UpdateReport
        WITH SOURCE_TOPIC "energy/openadr/report/update"
        WITH DESTINATION_TOPIC "energy/openadr/report/update/response"
        WITH METHOD "PUT"
        WITH ENDPOINT "/openadr3/3.0.1/reports/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: DeleteReport changes VTN state. Restrict who can publish to energy/openadr/report/delete.
    ADD EVENT DeleteReport
        WITH SOURCE_TOPIC "energy/openadr/report/delete"
        WITH DESTINATION_TOPIC "energy/openadr/report/delete/response"
        WITH METHOD "DELETE"
        WITH ENDPOINT "/openadr3/3.0.1/reports/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    ADD EVENT ListSubscriptions
        WITH SOURCE_TOPIC "energy/openadr/subscription/list"
        WITH DESTINATION_TOPIC "energy/openadr/subscription/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/openadr3/3.0.1/subscriptions?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    ADD EVENT GetSubscription
        WITH SOURCE_TOPIC "energy/openadr/subscription/get"
        WITH DESTINATION_TOPIC "energy/openadr/subscription/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/openadr3/3.0.1/subscriptions/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    # WRITE: CreateSubscription changes VTN state. Restrict who can publish to energy/openadr/subscription/create.
    ADD EVENT CreateSubscription
        WITH SOURCE_TOPIC "energy/openadr/subscription/create"
        WITH DESTINATION_TOPIC "energy/openadr/subscription/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/openadr3/3.0.1/subscriptions"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: UpdateSubscription changes VTN state. Restrict who can publish to energy/openadr/subscription/update.
    ADD EVENT UpdateSubscription
        WITH SOURCE_TOPIC "energy/openadr/subscription/update"
        WITH DESTINATION_TOPIC "energy/openadr/subscription/update/response"
        WITH METHOD "PUT"
        WITH ENDPOINT "/openadr3/3.0.1/subscriptions/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: DeleteSubscription changes VTN state. Restrict who can publish to energy/openadr/subscription/delete.
    ADD EVENT DeleteSubscription
        WITH SOURCE_TOPIC "energy/openadr/subscription/delete"
        WITH DESTINATION_TOPIC "energy/openadr/subscription/delete/response"
        WITH METHOD "DELETE"
        WITH ENDPOINT "/openadr3/3.0.1/subscriptions/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    ADD EVENT ListVens
        WITH SOURCE_TOPIC "energy/openadr/ven/list"
        WITH DESTINATION_TOPIC "energy/openadr/ven/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/openadr3/3.0.1/vens?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    ADD EVENT GetVen
        WITH SOURCE_TOPIC "energy/openadr/ven/get"
        WITH DESTINATION_TOPIC "energy/openadr/ven/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/openadr3/3.0.1/vens/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    # WRITE: CreateVen changes VTN state. Restrict who can publish to energy/openadr/ven/create.
    ADD EVENT CreateVen
        WITH SOURCE_TOPIC "energy/openadr/ven/create"
        WITH DESTINATION_TOPIC "energy/openadr/ven/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/openadr3/3.0.1/vens"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: UpdateVen changes VTN state. Restrict who can publish to energy/openadr/ven/update.
    ADD EVENT UpdateVen
        WITH SOURCE_TOPIC "energy/openadr/ven/update"
        WITH DESTINATION_TOPIC "energy/openadr/ven/update/response"
        WITH METHOD "PUT"
        WITH ENDPOINT "/openadr3/3.0.1/vens/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    ADD EVENT ListResources
        WITH SOURCE_TOPIC "energy/openadr/resource/list"
        WITH DESTINATION_TOPIC "energy/openadr/resource/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/openadr3/3.0.1/vens/{value.json.ven_id}/resources?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    ADD EVENT GetResource
        WITH SOURCE_TOPIC "energy/openadr/resource/get"
        WITH DESTINATION_TOPIC "energy/openadr/resource/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/openadr3/3.0.1/vens/{value.json.ven_id}/resources/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
    # WRITE: CreateResource changes VTN state. Restrict who can publish to energy/openadr/resource/create.
    ADD EVENT CreateResource
        WITH SOURCE_TOPIC "energy/openadr/resource/create"
        WITH DESTINATION_TOPIC "energy/openadr/resource/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/openadr3/3.0.1/vens/{value.json.ven_id}/resources"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: UpdateResource changes VTN state. Restrict who can publish to energy/openadr/resource/update.
    ADD EVENT UpdateResource
        WITH SOURCE_TOPIC "energy/openadr/resource/update"
        WITH DESTINATION_TOPIC "energy/openadr/resource/update/response"
        WITH METHOD "PUT"
        WITH ENDPOINT "/openadr3/3.0.1/vens/{value.json.ven_id}/resources/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
```

## Examples

Get a token: publish

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"body": "grant_type=client_credentials&client_id=ven_client_99&client_secret=<secret>"}
```

to `energy/openadr/auth/token` and read `access_token` from `energy/openadr/auth/token/response`.

Read the events of one program: publish

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"authorization": "Bearer <access_token>", "query": "programID=0&limit=50"}
```

to `energy/openadr/event/list`.

Report measured usage for an event: publish

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"authorization": "Bearer <access_token>", "body": {"programID": "0", "eventID": "12", "clientName": "porto-plant", "resources": [{"resourceName": "main-meter", "intervals": [{"id": 0, "payloads": [{"type": "USAGE", "values": [412.5]}]}]}]}}
```

to `energy/openadr/report/create`.

Actions that keep a token and poll events:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
# Example actions for the OpenAdr3Vtn route (docs page). Not part of the route template.
# Secrets: OPENADR_TOKEN_REQUEST = "grant_type=client_credentials&client_id=<id>&client_secret=<secret>" (form body
#   for auth/token) and OPENADR_AUTHORIZATION = "Bearer <access_token>" (current token; refresh it before expires_in).
# Program id "0" is a placeholder. Use an id from program/list.
# Inline JSON object literals are not supported when publishing to a topic, so the request payload is a COLLAPSED
#   model sent with PUBLISH MODEL.

DEFINE MODEL OpenAdrRequest COLLAPSED
    ADD STRING "authorization"
    ADD STRING "id"
    ADD STRING "query"

DEFINE MODEL OpenAdrTokenRequest COLLAPSED
    ADD STRING "body"

DEFINE ACTION OpenAdrTokenHourly
ON EVERY 1 HOUR DO
    PUBLISH MODEL OpenAdrTokenRequest TO "energy/openadr/auth/token" WITH
        body = (GET SECRET "OPENADR_TOKEN_REQUEST")

DEFINE ACTION OpenAdrEventsEvery5Minutes
ON EVERY 5 MINUTES DO
    PUBLISH MODEL OpenAdrRequest TO "energy/openadr/event/list" WITH
        authorization = (GET SECRET "OPENADR_AUTHORIZATION")
        id = ""
        query = "programID=0&limit=50"

DEFINE ACTION OpenAdrProgramsDaily
ON EVERY 1 DAY DO
    PUBLISH MODEL OpenAdrRequest TO "energy/openadr/program/list" WITH
        authorization = (GET SECRET "OPENADR_AUTHORIZATION")
        id = ""
        query = "limit=50"
```

Restrict who can publish to `energy/openadr/#` with a rule, because these payloads carry the client secret and token.

## Notifications (webhooks)

Instead of polling, create a subscription with `subscription/create`. Its `objectOperations[].callbackUrl` points to a Coreflux REST API **server** connector (`ENABLE_SERVER`), and the VTN posts `{"objectType", "operation", "object"}` to it whenever an event, program or report changes. OpenADR 3.1 also lets a VTN publish these notifications to an MQTT broker, which fits a Coreflux broker directly.

## Notes

* **Versions:** this connector follows OpenADR **3.0.1**. OpenADR 3.1.0 (August 2025) keeps the same objects but uses a `targets` query parameter, moves resources to a top-level `/resources` path, and adds `auth/server` and MQTT notifiers. Check which version your VTN runs.
* **Spec source:** the OpenAPI document is published by the OpenADR Alliance (Apache 2.0). A public copy is on [GitHub](https://github.com/grid-coordination/openadr3-specification).
* **Polling:** DR events are usually published hours or a day ahead. Polling `event/list` every 5 minutes is plenty; subscriptions remove the need to poll.
* **Testing status:** the connector has only been tested against the specification and a local mock VTN. Results against a real VTN are still to come.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.