# Code samples

## Overview

Using the Switch OpenADR 3 VTN API is as simple as communicating with any REST API. Below you will find examples of how to authenticate with the VTN prior to making any request to it and it follows with example of how to make a simple request.

Information about the VTN URL you can find on the [VTN Endpoint](/openadr-3/overview.md#vtn-endpoint) page, details about the authentication parameters at [Token Endpoint](/openadr-3/authentication/token-endpoint.md), and information about the available API endpoints at [API Reference](/openadr-3/api-reference.md) page.

{% hint style="danger" %}
Samples shown below are not meant to be production-ready code or to be used as-is. They are meant to illustrate the use of an API or feature in the simplest way possible. For this reason, these samples make certain assumptions on hosting, authentication, request/response handling etc.
{% endhint %}

{% hint style="danger" %}
It is of utmost importance that the fetched access token is cached by the client for the duration of its validity and reused for subsequent requests. The Switch Authorization Server will soon start imposing rate limits on the token endpoint which when reached will reject the requests to fetch an access token for a given time period.

Please note that the samples provided below do not do any caching of the access token, they are as stated, just a sample that should not be used in production environments.
{% endhint %}

## Samples

{% tabs %}
{% tab title="C#" %}

```csharp
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace ConsoleAppOadr3SamplesDocs
{
    internal class Application
    {
        private const string TokenProviderUrl = "<token_provider_url>";
        private const string ClientId = "<your_client_id>";
        private const string ClientSecret = "<your_client_secret>";
        private const string GrantType = "client_credentials";
        private const string Audience = "<audience>";
        private const string VtnUrl = "<vtn_url>";

        static async Task Main()
        {
            // NOTE: This is just a very simple example for authenticating.
            // Ideally you would use an established library instead, allowing token re-use
            // and refresh token when needed.
            var accessToken = await GetAccessToken();
            // NOTE: Another way to get access token is to call the Auth endpoint of the VTN but
            // this way is to be used for testing purposes only.
            
            // Search for available programs.
            using var client = new HttpClient();
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            var response = await client.GetAsync($"{VtnUrl}/programs");
            var content = await response.Content.ReadAsStringAsync();
            var programs = JsonSerializer.Deserialize<Program[]>(content);
            var program = programs.FirstOrDefault();
            
            // Search for available events with filters.
            response = await client.GetAsync($"{VtnUrl}/events?programID={program.Id}&skip=1&limit=5");
            content = await response.Content.ReadAsStringAsync();
            var events = JsonSerializer.Deserialize<Event[]>(content);

            // Create subscription.
            var subscription = new Subscription
            {
                ProgramId = program.Id,
                ClientName = "MyVEN",
                ObjectOperations = new List<SubscriptionObjectOperation>
                {
                    new()
                    {
                        Objects = new List<string> { "EVENT", "REPORT" },
                        Operations = new List<string> { "POST", "PUT", "DELETE" },
                        CallbackUrl = "https://mycompany.com/callback"
                    }
                }
            };
            var payload = new StringContent(JsonSerializer.Serialize(subscription), Encoding.UTF8, "application/json");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Add("Accept", "application/json");
            await client.PostAsync($"{VtnUrl}/subscriptions", payload);
        }

        private static async Task<string> GetAccessToken()
        {
            using var client = new HttpClient();
            var response = await client.PostAsync(TokenProviderUrl, new FormUrlEncodedContent(new Dictionary<string, string>
            {
                { "grant_type", GrantType },
                { "client_id", ClientId },
                { "client_secret", ClientSecret },
                { "audience", Audience }
            }));
            var content = await response.Content.ReadAsStringAsync();
            var token = JsonSerializer.Deserialize<Token>(content);
            return token.AccessToken;
        }

        private class Token
        {
            [JsonPropertyName("access_token")]
            public string AccessToken { get; init; }
        }

        private class Program
        {
            [JsonPropertyName("id")]
            public string Id { get; set; }
            
            [JsonPropertyName("programName")]
            public string ProgramName { get; set; }

            // Remaining fields omitted for this example, see the API reference documentation for the full list.
        }

        private class Event
        {
            [JsonPropertyName("id")]
            public string Id { get; set; }
            
            [JsonPropertyName("programID")]
            public string ProgramId { get; set; }
            
            [JsonPropertyName("eventName")]
            public string EventName { get; set; }

            // Remaining fields omitted for this example, see the API reference documentation for the full list.
        }

        private class Subscription
        {
            [JsonPropertyName("clientName")]
            public string ClientName { get; set; }
            
            [JsonPropertyName("programID")]
            public string ProgramId { get; set; }
            
            [JsonPropertyName("objectOperations")]
            public List<SubscriptionObjectOperation> ObjectOperations { get; set; }

            // Remaining fields omitted for this example, see the API reference documentation for the full list.
        }

        private class SubscriptionObjectOperation
        {
            [JsonPropertyName("objects")]
            public List<string> Objects { get; set; }
            
            [JsonPropertyName("operations")]
            public List<string> Operations { get; set; }
            
            [JsonPropertyName("callbackUrl")]
            public string CallbackUrl { get; set; }

            // Remaining fields omitted for this example, see the API reference documentation for the full list.
        }
    }
}
```

{% endtab %}

{% tab title="NodeJS" %}

```javascript
const VTN_URL = 'https://qa-vtn3.switchmarket.se/api/openadr/v1';
const POLL_MS = 10_000;

// See https://developer.switchmarket.se/openadr-3/authentication/vtn-credentials
// and https://developer.switchmarket.se/openadr-3/authentication/token-endpoint
const TOKEN_URL = '';
const AUDIENCE = '';
const CLIENT_ID = '';
const CLIENT_SECRET = '';

async function run() {
    // A proper integration should utilize an oauth client library to be able to utilize refresh tokens
    // and more, e.g https://github.com/panva/node-openid-client
    const tokenResponse = await fetch(TOKEN_URL, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': 'Basic ' + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')
        },
        body: `grant_type=client_credentials&audience=${AUDIENCE}`
    });
    if (!tokenResponse.ok) {
        console.error(await tokenResponse.json());
        process.exit(1);
    }
    const token = (await tokenResponse.json()).access_token;
    async function readEvents() {
        const params = new URLSearchParams({ 'x-start': new Date().toISOString() }).toString();
        const eventResponse = await fetch(`${VTN_URL}/events?${params}`, {
            headers: {
                authorization: `Bearer ${token}`
            }
        });
        if (!eventResponse.ok) {
            console.error(await eventResponse.json());
            process.exit(1);
        }
        const events = await eventResponse.json();

        if (events.length > 0) {
            console.log();
            console.log(`Found ${events.length} event(s)...`)
        } else {
            console.log(`Found no events... Trying again in ${POLL_MS} ms`);
        }

        for (const event of events) {
            const resourceId = event.targets.find(t => t.type === 'RESOURCE_NAME').values[0];
            const payloadLimitVersion = event.intervals[0].payloads.find(p => p.type === 'POWER_LIMIT_VERSION').values[0];

            console.log();
            console.log(`ID = ${event.id}`);
            console.log(`RESOURCE ID = ${resourceId}`)
            console.log(`START = ${event.intervalPeriod.start}`);
            console.log(`DURATION = ${event.intervalPeriod.duration}`);
            // For a production asset the payload type will instead be 'PRODUCTION_POWER_LIMIT'
            console.log(`LIMIT = ${event.intervals[0].payloads.find(p => p.type === 'CONSUMPTION_POWER_LIMIT').values[0]} KW`);
            console.log(`LIMIT VERSION = ${payloadLimitVersion}`);

            // See https://developer.switchmarket.se/openadr-3/payloads#openadr-acknowledgement-report
            if (event.reportDescriptors.some(r => r.payloadType === 'POWER_LIMIT_ACKNOWLEDGEMENT')) {
                const params = new URLSearchParams({ 'eventID': event.id }).toString();
                const reportResponse = await fetch(`${VTN_URL}/reports?${params}`, {
                    headers: {
                        authorization: `Bearer ${token}`
                    }
                });
                if (!reportResponse.ok) {
                    console.error(await reportResponse.json());
                    process.exit(1);
                }
                const reports = await reportResponse.json();

                // Check for existing acknowledgement - each event should only be acknowledged once for each power limit version
                const acks = reports.filter(r => r.payloadDescriptors.some(pd => pd.payloadType === 'POWER_LIMIT_ACKNOWLEDGEMENT'));
                const ackedVersions = acks.map(r => r.resources[0].intervals[0].payloads.find(p => p.type === 'POWER_LIMIT_ACKNOWLEDGEMENT').values[0]);
                if (!ackedVersions.includes(payloadLimitVersion)) {
                    console.log();
                    if (acks.length > 0) {
                        console.log(`Event has been acknowledged for power limit version(s) ${ackedVersions.join(', ')}, but the version is now ${payloadLimitVersion}`);
                    }
                    console.log('Sending acknowledgement...');
                    const ackResponse = await fetch(`${VTN_URL}/reports`, {
                        method: 'POST',
                        headers: {
                            authorization: `Bearer ${token}`,
                            'content-type': 'application/json'
                        },
                        body: JSON.stringify({
                            eventID: event.id,
                            programID: event.programID,
                            clientName: "<client name>",
                            payloadDescriptors: [{
                                payloadType: "POWER_LIMIT_ACKNOWLEDGEMENT"
                            }],
                            resources: [{
                                resourceName: resourceId,
                                intervalPeriod: event.intervalPeriod,
                                intervals: [{
                                    id: 0,
                                    payloads: [{
                                        type: "POWER_LIMIT_ACKNOWLEDGEMENT",
                                        values: [payloadLimitVersion]
                                    }]
                                }]
                            }]
                        })
                    });
                    if (!ackResponse.ok) {
                        console.error(await ackResponse.json());
                        process.exit(1);
                    } else {
                        console.log('Acknowledgement sent sucessfully');
                    }
                } else {
                    console.log();
                    console.log('Event already acknowledged');
                }
            }
        }
    }

    await readEvents();
    setInterval(readEvents, POLL_MS);
}

run();
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
The Switch platform has an official client library for .NET which you can find [here](/libraries/.net-sdk.md).
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developer.switchmarket.se/openadr-3/code-samples.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
