# REST API

> Sign in with a user name and password, get a token, and read or change Proxus objects such as devices, gateways and rules over OData.

Source: https://proxus.io/docs/develop/rest-api

At the end of this guide, a script of yours signs in to the hub, gets a token and reads devices with their
gateway. The same API can create, change and delete objects, within the rights of the user you sign in with.

## Before you start

You need:

- The hub's address, for example `http://localhost:8080` on a local installation. The API is served on the same
  port as the hub's screens.
- A Proxus user for the integration. Its role decides what the API may read and change, exactly as in the
  screens. See [Users and roles](https://proxus.io/docs/administer/users-and-roles).
- `curl`, Python 3 with the `requests` package, or .NET, depending on the example you follow.

## What the API offers

| Address | What it does |
| --- | --- |
| `POST /api/Authentication/Authenticate` | Checks a user name and password and returns a token. |
| `/api/odata/<EntitySet>` | Read, create, change and delete objects (OData v4). |
| `/api/odata/$metadata` | The full data model: every entity set, its fields and its links. |
| `/swagger` | An interactive page that lists every operation and lets you try it. |
| `POST /api/Telemetry/execute` | Stored tag values. See [Telemetry API](https://proxus.io/docs/develop/telemetry-api). |

Among the entity sets are `Device`, `DeviceProfile`, `DeviceProfileRegister` (tags), `Gateway`, `TargetProfile`
(outbound targets), `NotificationChannel`, `Rule`, `RuleAction`, `Flow` (pipelines), `Function`, `Alert`, `Log`,
`ScheduledDelivery`, `TrendView` and `UnsTopicPermission`. The list at `/api/odata/` is always the complete one.

## Step 1: Get a token

Send the user name and password as JSON. The answer is the token itself, as plain text.

```bash
curl -X POST http://localhost:8080/api/Authentication/Authenticate \
  -H "Content-Type: application/json" \
  -d '{"userName":"Admin","password":""}'
```

Put the token in the `Authorization` header of every later call:

```text
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

- A wrong user name or password returns `401` with the text `User name or password is incorrect.`
- A call to `/api/...` without a valid token is answered with `401`, and a call the user may not make with `403`.
- A token stays valid for the time set in `TokenExpireTime` (one year in the delivered configuration). Get a new
  one when a call answers `401`.

> **Warning:**
Tokens are signed with a key that the hub makes for itself on its first start and keeps in the shared
configuration folder (`Keys/UI/jwt-signing.key`). Anyone who has that file can create valid tokens: protect the
folder like a password. Set your own key in `IssuerSigningKey` only if another system must check the tokens. See
[Configuration keys](https://proxus.io/docs/reference/configuration#webapi).

## Step 2: Read objects

```bash
TOKEN=$(curl -s -X POST http://localhost:8080/api/Authentication/Authenticate \
  -H "Content-Type: application/json" -d '{"userName":"Admin","password":""}')

curl -s -G http://localhost:8080/api/odata/Device \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "\$filter=startswith(DeviceName,'LYS')" \
  --data-urlencode "\$select=Oid,DeviceName,Enabled" \
  --data-urlencode "\$count=true" \
  --data-urlencode "\$top=3"
```

The answer:

```json
{
  "@odata.context": "http://localhost:8080/api/odata/$metadata#Device(Oid,DeviceName,Enabled)",
  "@odata.count": 14,
  "value": [
    { "Oid": 66, "DeviceName": "LYS_MILL_01", "Enabled": true },
    { "Oid": 67, "DeviceName": "LYS_LINE1_PLC", "Enabled": true },
    { "Oid": 69, "DeviceName": "LYS_PACKER_04", "Enabled": true }
  ]
}
```

The usual OData query options work:

| Option | Example | Notes |
| --- | --- | --- |
| `$filter` | `Site eq 'Lyon Foods Plant'` | `eq`, `ne`, `gt`, `lt`, `and`, `or`, `startswith`, `contains` |
| `$select` | `Oid,DeviceName,Status` | Only these fields. |
| `$expand` | `Gateway($select=Name)` | Follow a link to another object. |
| `$orderby` | `DeviceName desc` | |
| `$top`, `$skip` | `$top=50&$skip=100` | `$top` may be at most 100. A larger value is refused with `400`. |
| `$count` | `$count=true` | Adds `@odata.count` with the total. |

> **Warning:**
Without `$top`, a query returns every matching object in one answer. On large sets, such as `Log` or
`DeviceProfileRegister`, page with `$top` and `$skip` and keep `$select` short.

One object is read by its key: `GET /api/odata/Device(69)`. A device's latest values come with it when you expand
`LatestValues`:

```bash
curl -s -G "http://localhost:8080/api/odata/Device(69)" -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "\$select=DeviceName" --data-urlencode "\$expand=LatestValues"
```

Each latest value has `Key`, `Value`, `DataType`, `Quality` and `LastUpdate`.

## Step 3: Use it from code

```python
import requests

HUB = "http://localhost:8080"

# 1. Sign in and get a token
response = requests.post(
    f"{HUB}/api/Authentication/Authenticate",
    json={"userName": "Admin", "password": ""},
    timeout=30,
)
response.raise_for_status()
token = response.text.strip()
headers = {"Authorization": f"Bearer {token}"}

# 2. Read the devices of one site with their gateway
response = requests.get(
    f"{HUB}/api/odata/Device",
    params={
        "$filter": "Site eq 'Lyon Foods Plant'",
        "$select": "Oid,DeviceName,Status",
        "$expand": "Gateway($select=Name)",
        "$orderby": "DeviceName",
    },
    headers=headers,
    timeout=30,
)
response.raise_for_status()
for device in response.json()["value"]:
    print(device["Oid"], device["DeviceName"], device["Status"], device["Gateway"]["Name"])
```
```csharp
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

var hub = new HttpClient { BaseAddress = new Uri("http://localhost:8080/") };

// 1. Sign in and get a token
var login = await hub.PostAsJsonAsync("api/Authentication/Authenticate",
    new { userName = "Admin", password = "" });
login.EnsureSuccessStatusCode();
var token = await login.Content.ReadAsStringAsync();
hub.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

// 2. Read the gateways
using var gateways = JsonDocument.Parse(
    await hub.GetStringAsync("api/odata/Gateway?$select=Oid,Name"));
foreach (var gateway in gateways.RootElement.GetProperty("value").EnumerateArray())
    Console.WriteLine($"{gateway.GetProperty("Oid")} {gateway.GetProperty("Name")}");
```

## Create, change and delete

The entity sets also take `POST` (create), `PATCH` (change some fields), `PUT` (replace) and `DELETE`, the standard
OData way. For example, to switch a device off:

```bash
curl -X PATCH "http://localhost:8080/api/odata/Device(69)" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"Enabled": false}'
```

The user's role decides what is allowed. A change the role does not permit is refused, exactly as the same change
would be refused in the hub's screens.

> **Tip:**
Open `http://<hub>:8080/swagger`, select **Authorize** and paste a token. Every operation of every entity set is
listed there with its fields, and you can try calls from the browser.

## What's next

- [Telemetry API](https://proxus.io/docs/develop/telemetry-api): stored tag values, with SQL.
- [MQTT and the UNS topics](https://proxus.io/docs/develop/mqtt): live values as they are read.
- [Network ports and endpoints](https://proxus.io/docs/reference/ports): every address the hub and gateways listen on.
