API Documentation

PestScan PC4.0 API Documentation

A frontend reference for authentication, event ingestion, request payloads, headers, response shapes, and common integration pitfalls.

Version

v1.0

Auth

JWT Bearer

Overview

This page mirrors the public API documentation inside the frontend so users do not need Swagger or source access to understand the integration surface.

Base URL
Use https://iot.pestscan.eu as the root for every API call.
Protected Access
Protected endpoints expect a Bearer token in the Authorization header.
Event Ingestion
Event creation also requires a pco-id request header.
Recommended integration order
If you are integrating for the first time, follow this sequence to get to a valid event submission quickly.

Step 1

Review endpoints

Confirm the base URL, public routes, and which calls require authentication.

Step 2

Authenticate

Call Login first, then keep the returned refresh token available for token renewal.

Step 3

Send events

Use the Bearer token and include the required pco-id header for event ingestion.

Need the account-side setup steps?
The PC40 setup guide walks through profile creation, customer and location configuration, trap assignment, and verification in PestScan.

Endpoints

The currently documented public endpoints are summarized below.

Use this base URL for the API
Start every PestScan PC4.0 API request with this base URL, then append one of the endpoint paths shown below.

API Base URL

https://iot.pestscan.eu

Example endpoint:
https://iot.pestscan.eu/api/v1.0/Event
Login
Exchange username and password for access and refresh tokens.
MethodPOST
Endpoint
/api/v1.0/Authentication/Login
Auth requiredNo
Refresh Token
Refresh an expired access token using a valid refresh token pair.
MethodPOST
Endpoint
/api/v1.0/Authentication/Refresh
Auth requiredNo
Create Event(s)
Submit one or more events to the ingestion API using the pco-id header.
MethodPOST
Endpoint
/api/v1.0/Event
Auth requiredYes

Authentication

The API uses JWT access tokens plus refresh tokens.

Recommended Flow
  1. Call POST /Authentication/Login with username and password.
  2. Store the returned accessToken and refreshToken.
  3. Send Authorization: Bearer <access_token> to protected endpoints.
  4. Refresh the session through POST /Authentication/Refresh when the access token expires.
HTTP Header
Authorization: Bearer <access_token>
Login Endpoint
Use this endpoint to exchange username and password credentials for an access token and refresh token.
MethodPOST
Endpoint
/api/v1.0/Authentication/Login

Request body

json
{
  "message_id": "f3a84b8d-9e54-4b89-9d95-c9348807e55d",
  "date": "2026-03-11T12:32:33.994Z",
  "body": {
    "username": "user@example.com",
    "password": "your-password"
  }
}

Success response

json
{
  "message_id": "f3a84b8d-9e54-4b89-9d95-c9348807e55d",
  "date": "2026-02-19T13:45:00Z",
  "body": {
    "accessToken": "<jwt-access-token>",
    "refreshToken": "<jwt-refresh-token>"
  },
  "is_successful": true
}

Common error

json
{
  "message_id": "...",
  "date": "...",
  "is_successful": false,
  "error": {
    "message": "Username or password incorrect.",
    "code": "1",
    "details": "The username or password provided is incorrect."
  }
}
Example code

cURL

Bash
curl -X POST "https://iot.pestscan.eu/api/v1.0/Authentication/Login" \
  -H "Content-Type: application/json" \
  -d '{
    "message_id": "f3a84b8d-9e54-4b89-9d95-c9348807e55d",
    "date": "2026-03-11T12:32:33.994Z",
    "body": {
      "username": "user@example.com",
      "password": "your-password"
    }
  }'

Python requests

Python
import requests

payload = {
    "message_id": "f3a84b8d-9e54-4b89-9d95-c9348807e55d",
    "date": "2026-03-11T12:32:33.994Z",
    "body": {
        "username": "user@example.com",
        "password": "your-password",
    },
}

response = requests.post(
    "https://iot.pestscan.eu/api/v1.0/Authentication/Login",
    json=payload,
)

data = response.json()

JavaScript fetch

JavaScript
const response = await fetch("https://iot.pestscan.eu/api/v1.0/Authentication/Login", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    message_id: "f3a84b8d-9e54-4b89-9d95-c9348807e55d",
    date: "2026-03-11T12:32:33.994Z",
    body: {
      username: "user@example.com",
      password: "your-password",
    },
  }),
});

const data = await response.json();

C# HttpClient

C#
using System.Net.Http.Headers;
using System.Text;

var client = new HttpClient();
var payload = """
{
  "message_id": "f3a84b8d-9e54-4b89-9d95-c9348807e55d",
  "date": "2026-03-11T12:32:33.994Z",
  "body": {
    "username": "user@example.com",
    "password": "your-password"
  }
}
""";

var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://iot.pestscan.eu/api/v1.0/Authentication/Login", content);
var body = await response.Content.ReadAsStringAsync();
Field
Required
Type
Notes
message_id
Yes
string
Client-generated request identifier.
date
Yes
ISO 8601 string
Required wrapper timestamp.
body.username
Yes
string
Username for authentication. In practice this should be the user's email address.
body.password
Yes
string
User password.
Implementation notes

The username value should be the user's email address.

Usernames are normalized to lowercase during login.

Unverified users are rejected and can return error code 2.

Refresh Endpoint
Use this endpoint to exchange an expired access token and valid refresh token for a new pair.
MethodPOST
Endpoint
/api/v1.0/Authentication/Refresh

Request body

json
{
  "message_id": "f3a84b8d-9e54-4b89-9d95-c9348807e55d",
  "date": "2026-03-11T12:32:33.994Z",
  "body": {
    "accessToken": "<previous-access-token>",
    "refreshToken": "<current-refresh-token>"
  }
}

Success response

json
{
  "message_id": "f3a84b8d-9e54-4b89-9d95-c9348807e55d",
  "date": "2026-02-19T13:45:00Z",
  "body": {
    "accessToken": "<jwt-access-token>",
    "refreshToken": "<jwt-refresh-token>"
  },
  "is_successful": true
}

Common error

json
{
  "message_id": "...",
  "date": "...",
  "is_successful": false,
  "error": {
    "message": "Wrong username or refreshToken.",
    "code": "3",
    "details": "The username or refresh token provided is incorrect."
  }
}
Example code

cURL

Bash
curl -X POST "https://iot.pestscan.eu/api/v1.0/Authentication/Refresh" \
  -H "Content-Type: application/json" \
  -d '{
    "message_id": "f3a84b8d-9e54-4b89-9d95-c9348807e55d",
    "date": "2026-03-11T12:32:33.994Z",
    "body": {
      "accessToken": "<previous-access-token>",
      "refreshToken": "<current-refresh-token>"
    }
  }'

Python requests

Python
import requests

payload = {
    "message_id": "f3a84b8d-9e54-4b89-9d95-c9348807e55d",
    "date": "2026-03-11T12:32:33.994Z",
    "body": {
        "accessToken": "<previous-access-token>",
        "refreshToken": "<current-refresh-token>",
    },
}

response = requests.post(
    "https://iot.pestscan.eu/api/v1.0/Authentication/Refresh",
    json=payload,
)

data = response.json()

JavaScript fetch

JavaScript
const response = await fetch("https://iot.pestscan.eu/api/v1.0/Authentication/Refresh", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    message_id: "f3a84b8d-9e54-4b89-9d95-c9348807e55d",
    date: "2026-03-11T12:32:33.994Z",
    body: {
      accessToken: "<previous-access-token>",
      refreshToken: "<current-refresh-token>",
    },
  }),
});

const data = await response.json();

C# HttpClient

C#
using System.Text;

var client = new HttpClient();
var payload = """
{
  "message_id": "f3a84b8d-9e54-4b89-9d95-c9348807e55d",
  "date": "2026-03-11T12:32:33.994Z",
  "body": {
    "accessToken": "<previous-access-token>",
    "refreshToken": "<current-refresh-token>"
  }
}
""";

var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://iot.pestscan.eu/api/v1.0/Authentication/Refresh", content);
var body = await response.Content.ReadAsStringAsync();
Field
Required
Type
Notes
message_id
Yes
string
Client-generated request identifier.
date
Yes
ISO 8601 string
Required wrapper timestamp.
body.accessToken
Yes
string
Previous access token.
body.refreshToken
Yes
string
Current refresh token.
Implementation notes

The refresh request does not require an Authorization header.

The access token and refresh token must belong to the same user context.

Events

Use this section to understand event ingestion, required headers, payload structure, and expected responses.

Create Event Endpoint
Use this endpoint to submit one or more events to the PC4.0 ingestion API.
MethodPOST
Endpoint
/api/v1.0/Event
Auth requiredYes

Required Headers

These headers are required when sending new events to the ingestion endpoint.

Required header for new events

When creating new events through POST /api/v1.0/Event, the request must include a pco-id header.

If pco-id is missing, the API returns 400 and the event will not be accepted.

HTTP Header
pco-id: <pco_id>
Request Headers
HTTP Headers
Authorization: Bearer <access_token>
pco-id: <pco_id>
Content-Type: application/json
Minimum Accepted Fields

Root: message_id, date, body, body.events

Per event: event_id, duid, event_type

date inside the event object is optional but recommended for realistic testing.

Minimal event request

json
{
  "message_id": "8890fdf8-e31d-4c6a-80d0-84fee8b1627d",
  "date": "2026-02-19T13:45:00Z",
  "body": {
    "events": [
      {
        "event_id": "evt-001",
        "duid": "device-123",
        "event_type": 901
      }
    ]
  }
}

Full event example

json
{
  "message_id": "c6d8e843-9789-4d8c-af18-3fe4fb0ef30f",
  "date": "2026-02-19T13:45:00Z",
  "body": {
    "events": [
      {
        "event_id": "evt-001",
        "duid": "device-123",
        "vendor_code": "vendor-a",
        "date": "2026-02-19T13:40:00Z",
        "status": 1,
        "battery_level": 91.5,
        "signal_intensity": "-68dBm",
        "event_type": 901,
        "location_id": "loc-22",
        "message": "Heartbeat",
        "unknown_attributes": {
          "any_key": "any_value"
        }
      }
    ]
  }
}

Success response

json
{
  "message_id": "...",
  "date": "...",
  "body": {
    "message": "Event(s) stored.",
    "event": {
      "message_id": "...",
      "date": "...",
      "body": {
        "events": [
          {
            "event_id": "evt-001"
          }
        ]
      }
    }
  },
  "is_successful": true
}
Example code

cURL

Bash
curl -X POST "https://iot.pestscan.eu/api/v1.0/Event" \
  -H "Authorization: Bearer <access_token>" \
  -H "pco-id: <pco_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "message_id": "8890fdf8-e31d-4c6a-80d0-84fee8b1627d",
    "date": "2026-02-19T13:45:00Z",
    "body": {
      "events": [
        {
          "event_id": "evt-001",
          "duid": "device-123",
          "event_type": 901
        }
      ]
    }
  }'

Python requests

Python
import requests

payload = {
    "message_id": "8890fdf8-e31d-4c6a-80d0-84fee8b1627d",
    "date": "2026-02-19T13:45:00Z",
    "body": {
        "events": [
            {
                "event_id": "evt-001",
                "duid": "device-123",
                "event_type": 901,
            }
        ]
    },
}

response = requests.post(
    "https://iot.pestscan.eu/api/v1.0/Event",
    headers={
        "Authorization": "Bearer <access_token>",
        "pco-id": "<pco_id>",
    },
    json=payload,
)

data = response.json()

JavaScript fetch

JavaScript
const response = await fetch("https://iot.pestscan.eu/api/v1.0/Event", {
  method: "POST",
  headers: {
    Authorization: "Bearer <access_token>",
    "pco-id": "<pco_id>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    message_id: "8890fdf8-e31d-4c6a-80d0-84fee8b1627d",
    date: "2026-02-19T13:45:00Z",
    body: {
      events: [
        {
          event_id: "evt-001",
          duid: "device-123",
          event_type: 901,
        },
      ],
    },
  }),
});

const data = await response.json();

C# HttpClient

C#
using System.Net.Http.Headers;
using System.Text;

var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "<access_token>");
client.DefaultRequestHeaders.Add("pco-id", "<pco_id>");

var payload = """
{
  "message_id": "8890fdf8-e31d-4c6a-80d0-84fee8b1627d",
  "date": "2026-02-19T13:45:00Z",
  "body": {
    "events": [
      {
        "event_id": "evt-001",
        "duid": "device-123",
        "event_type": 901
      }
    ]
  }
}
""";

var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://iot.pestscan.eu/api/v1.0/Event", content);
var body = await response.Content.ReadAsStringAsync();

Error responses

These are the main non-success responses to expect when creating events.

Missing pco-id response
Returned when the required pco-id header is omitted.
json
{
  "message_id": "...",
  "date": "...",
  "is_successful": false,
  "error": {
    "message": "PCO ID missing.",
    "code": "1",
    "details": "The required pco-id header was not provided."
  }
}
Duplicate event response
Returned when an event_id has already been stored.
json
{
  "message_id": "...",
  "date": "...",
  "is_successful": false,
  "error": {
    "message": "Event already exists.",
    "code": "2",
    "details": "An event with the same event_id has already been stored."
  }
}
Forwarding retry response
Returned when storage succeeds but forwarding still fails.
json
{
  "message_id": "...",
  "date": "...",
  "is_successful": false,
  "error": {
    "message": "Event forwarding returned an error.",
    "code": "200",
    "details": "The event may already be stored even though the forwarding step failed."
  }
}
Generic validation response
Returned when the JSON shape is invalid or a field cannot be deserialized.
json
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "request": [
      "The request field is required."
    ],
    "$.body.events[0].unknown_attributes": [
      "The JSON value could not be converted to System.Text.Json.Nodes.JsonObject."
    ]
  },
  "traceId": "00-00000000000000000000000000000000-0000000000000000-00"
}

Envelope fields

Field
Required
Type
Notes
message_id
Yes
string
Top-level wrapper identifier.
date
Yes
ISO 8601 string
Required wrapper timestamp.
body.events
Yes
EventRequest[]
At least one event object.

Event item fields

Field
Required
Type
Notes
event_id
Yes
string
Must be unique or backend returns duplicate error.
duid
Yes
string
Device unique identifier.
event_type
Yes
number
Use one of the documented event type codes.
vendor_code
No
string | null
Vendor identifier if available.
date
No
ISO 8601 string | null
Event timestamp.
status
No
number | null
Optional status code.
battery_level
No
number | null
Battery level value.
signal_intensity
No
string | null
Signal representation such as -68dBm.
location_id
No
string | null
Optional, but recommended. Use it to link the event directly to the correct PestScan location.
message
No
string | null
Human-readable event message.
attachments
No
Attachment[] | null
Optional file payloads.
common_attributes
No
object | null
Structured sensor details.
unknown_attributes
No
object | null
Must be a JSON object, not an array.

Reference Types

Reference values used across the API payloads and common attributes.

Event Types

0Unknown
100Trap Fired
101Trap Fired With Catch
102Trap Fired Without Catch
103Trap Set Ready
104Trap Serviced
105Trap Fired Incorrectly
200Motion
201Heavy Motion
202Vibration
300Monitor
900Connection Interrupted
901Heartbeat
902Heartbeat Failed

Units of Measure (UOM)

1Gram(s)
2Kilogram(s)
3Ton(s)
4Milliliter(s)
5Liter(s)
6Cubic meter(s)
7Millimeter(s)
8Centimeter(s)
9Inch(es)
10Meter(s)
11Square meter(s)
13Fahrenheit
14Celsius

Status Types

ID
Description
Example
0
Offline
Not in use
1
Online
In Use

Molecules

ID
Abbreviation
Full name
1
pH
Potential of hydrogen
2
Cl
Chlorine
100
CO2
Carbon dioxide
101
NO2
Nitrogen dioxide

Common Pitfalls

These are the mistakes most likely to cause confusing 400 responses.

JSON must be valid JSON
Do not include inline comments like // example in request bodies.
unknown_attributes must be an object
The backend expects unknown_attributes to deserialize to a JSON object, not an array.
eventMessageWrapper errors can be secondary
If the request body cannot deserialize, ASP.NET may also report the top-level wrapper parameter as missing.
Use fresh event IDs for tests
Reusing the same event_id will trigger duplicate validation and return 400.