API Documentation

Global Settings

Base URL

The base URL for all API endpoints is: https://api.echelonic.cc/:site_id

Authentication

To authenticate, all requests must include an Authorization header containing a bearer token.
The header should be formatted as: Authorization: Bearer YOUR_ACCESS_TOKEN.

If the key is missing or invalid, the API will return a 401 Unauthorized response. Certain administrative endpoints require a special YOUR_ADMIN_TOKEN.

Usage Limits

Our API enforces a rate limit to ensure a reliable, stable, and fair experience for all users. Each API key is permitted up to 120 requests per minute. This limit applies to all endpoints across the API.

If you exceed the limit, your subsequent requests within that minute will be temporarily blocked, and you will receive an 429 Too Many Requests response.

Gateways

GET /gateways

List all gateways.

Response Body:

{
    "gateways": [
        {
            "id": 7,
            "mac_address": "083AF23A1390",
            "pos_x": "1",
            "pos_y": "10",
            "name": "gateway-name",
            "is_active": 1,
            "created_at": "timestamp",
            "updated_at": "timestamp"
        },
        ...
    ]
}

Responses:

  • 200 OK: Returns an array of gateway objects.
  • 500 Internal Server Error: Error retrieving the gateways.

POST /gateways

Add a new gateway.

Request Body:

{
  "mac_address": "001122334455",
  "pos_x": 10,
  "pos_y": 20,
  "name": "Gateway 1",
}

Response Body:

{
  "id": 123,
  "message": "Gateway added and assigned successfully"
}

Responses:

  • 201 Created: Gateway added successfully.
  • 400 Bad Request: Missing required fields or duplicate MAC address.
  • 500 Internal Server Error: Error adding the gateway.

PUT /gateways/:id

Update one or more fields of a specific gateway. Allowed fields are: mac_address, pos_x, pos_y, name, is_active.

Request Body Example:

{
  "name": "Updated Gateway",
  "is_active": 1
}

Responses:

  • 200 OK: Gateway updated successfully.
  • 400 Bad Request: No valid fields provided for update.
  • 404 Not Found: Gateway not found.
  • 500 Internal Server Error: Error updating the gateway.

DELETE /gateways/:id

Delete a gateway specified by its ID.

Responses:

  • 200 OK: Gateway and its assignment deleted successfully.
  • 404 Not Found: Gateway not found.
  • 500 Internal Server Error: Error deleting the gateway.

Fingerprints

Location fingerprinting is a high-precision task that relies on accurate, real-world signal measurements. While the API supports managing fingerprints, we strongly recommend using our specialized plugin, echelonic.planner.js, for the initial on-site collection to ensure data accuracy. You can find its documentation here: echelonic.planner.js.

GET /fingerprints

Retrieves all recorded fingerprints from the database.

Response Body:

{
  "fingerprints": [
    {
      "id": 1,
      "location": {
        "x": 10.5,
        "y": 20.2
      },
      "readings": {
        "GW_MAC_1": {
          "mean": -75.5,
          "stdDev": 2.5
        },
        "GW_MAC_2": {
          "mean": -80.1,
          "stdDev": 3.1
        }
      }
    },
    ...
  ]
}

Responses:

  • 200 OK: Returns an array of fingerprint objects.
  • 500 Internal Server Error: Failed to retrieve fingerprints.

POST /fingerprints

Initiates a real-time fingerprint collection process with a reference tag at a given location. This endpoint uses Server-Sent Events (SSE) to provide progress updates.

Request Body:

{
  "tag": "REFERENCE_TAG_ID",
  "pos_x": 10.5,
  "pos_y": 20.2,
  "minReadings": 20
}

Responses:

  • 200 OK (event-stream): Streams progress events. The final event will be `done` on success or `error` on failure.
  • 400 Bad Request: Missing required fields in the request body.

DELETE /fingerprints/:id

Deletes a specific fingerprint record by its ID.

Responses:

  • 200 OK: Fingerprint deleted successfully.
  • 404 Not Found: Fingerprint with the provided ID not found.
  • 500 Internal Server Error: Failed to delete the fingerprint.

POST /fingerprints/reload

Sends a signal to the worker process to reload the fingerprint cache for the current site.

Responses:

  • 202 Accepted: Reload signal sent successfully.
  • 403 Forbidden: Invalid or missing admin API key.
  • 500 Internal Server Error: Failed to send the reload signal.

Geofences

GET /geofences

Retrieve a list of all geofences.

Response Body:

{
    "geofences": [
        {
            "id": 19,
            "name": "geofence-name",
            "vertices": "[[0,11],[5,11],[5,5],[0,5]]",
            "is_active": 1,
            "created_at": "timestamp",
            "updated_at": "timestamp"
        },
        ...
    ]
}

Responses:

  • 200 OK: Returns an array of geofence objects.
  • 500 Internal Server Error: Error retrieving the geofences.

POST /geofences

Add a new geofence.

Request Body:

{
  "name": "Geofence 1",
  "vertices": [[2.0, 2.0], [6.0, 2.0], [6.0, 5.0], [2.0, 5.0]],
  "is_active": 1
}

Response Body:

{
  "id": 456,
  "message": "Geofence added successfully"
}

Note:

If vertices is provided as an array, it will be stringified.

Responses:

  • 201 Created: Geofence added successfully.
  • 400 Bad Request: Missing required fields or invalid vertices format.
  • 500 Internal Server Error: Error adding the geofence.

PUT /geofences/:id

Update one or more fields of a geofence. Allowed fields are: name, vertices, and is_active.

Request Body Example:

{
  "name": "Updated Geofence",
  "is_active": false
}

Responses:

  • 200 OK: Geofence updated successfully.
  • 400 Bad Request: No valid fields provided or invalid vertices format.
  • 404 Not Found: Geofence not found.
  • 500 Internal Server Error: Error updating the geofence.

DELETE /geofences/:id

Delete a geofence by its ID.

Responses:

  • 200 OK: Geofence deleted successfully.
  • 404 Not Found: Geofence not found.
  • 500 Internal Server Error: Error deleting the geofence.

Tags

GET /tags

Retrieve a list of tag updates with optional filtering parameters.

Query Parameters:

  • tag_id - Filter by tag ID
  • geofence_id - Filter by geofence ID
  • geofence_name - Filter by geofence name
  • movement_state - Filter by movement state

Response Body:

{
    "tags": [
        {
            "id": 11897,
            "tag_id": "tag-id",
            "pos_x": "4",
            "pos_y": "7",
            "movement_state": "type-of-event",
            "geofence_id": 19,
            "geofence_name": "geofence-name",
            "timestamp": "timestamp"
        },
        ...
    ]
}

Responses:

  • 200 OK: Returns an array of tag update objects.
  • 500 Internal Server Error: Error retrieving the tag updates.

Tag History

GET /tag_history

Retrieve historical data for tags based on specified filters.

Query Parameters:

  • tag_id (optional) - Filter by a specific tag ID.
  • startDate (optional) - The start date for the data range (e.g., 2025-09-19T00:00:00Z).
  • endDate (optional) - The end date for the data range (e.g., 2025-09-20T00:00:00Z).

Response Body:

{
    "history": [
        {
            "id": 12345,
            "tag_id": "tag-id",
            "pos_x": "5.25",
            "pos_y": "8.10",
            "geofence_id": 19,
            "geofence_name": "Warehouse Zone A",
            "timestamp": "2025-09-20T10:30:00Z"
        },
        {
            "id": 12344,
            "tag_id": "tag-id",
            "pos_x": "5.20",
            "pos_y": "8.05",
            "geofence_id": 19,
            "geofence_name": "Warehouse Zone A",
            "timestamp": "2025-09-20T10:29:00Z"
        }
    ]
}

Responses:

  • 200 OK: Returns an array of historical tag data.
  • 500 Internal Server Error: Failed to retrieve tag history.

Webhooks

GET /webhooks

Retrieve a list of registered webhooks.

Response Body:

{
    "webhooks": [
        {
            "id": 2,
            "tag_id": null,
            "geofence_id": null,
            "event_type": "type-of-event",
            "url": "https://your.callback.url/[tag_id]/[event_type]/[geofence_id]",
            "token_key": null,
            "is_active": 1,
            "created_at": "timestamp",
            "updated_at": "timestamp"
        },
        ...
    ]
}

Responses:

  • 200 OK: Returns an array of webhook objects.
  • 500 Internal Server Error: Error retrieving the webhooks.

POST /webhooks

Register a new webhook.

Event types:

Webhooks can be triggered by the following events started_moving, stopped_moving, is_moving, is_stopped, enter_geofence, exit_geofence, offline, online

Filters:

Webhooks can be filtered by tag_id and geofence_id, allowing subscriptions to specific tags or specific geofences.

Dynamic URL:

Webhook URLs can include dynamic placeholders [tag_id], [event_type] and [geofence_id] which are automatically replaced with actual values when the event is triggered.

Bear in mind:

The token_key field accepts a predefined identifier for your credential, not the secret API Key or Access Token itself.

Request Body:

{
  "tag_id": "filter-tag-id",
  "geofence_id": "filter-geofence-id",
  "event_type": "type-of-event",
  "url": "https://your.callback.url/[tag_id]/[event_type]/[geofence_id]",
  "token_key": "optional-key-reference",
  "is_active": 1
}

Responses:

  • 201 Created: Webhook added successfully.
  • 400 Bad Request: Missing required fields.
  • 500 Internal Server Error: Error adding the webhook.

PUT /webhooks/:id

Update an existing webhook. Allowed fields are: tag_id, geofence_id, event_type, url, token_key, and is_active.

The token_key field accepts a predefined identifier for your credential, not the secret API Key or Access Token itself.

Request Body Example:

{
  "url": "https://your.updated.callback.url/[tag_id]/[event_type]/[geofence_id]",
  "is_active": true
}

Responses:

  • 200 OK: Webhook updated successfully.
  • 400 Bad Request: No valid fields provided.
  • 404 Not Found: Webhook not found.
  • 500 Internal Server Error: Error updating the webhook.

DELETE /webhooks/:id

Delete a webhook by its ID.

Responses:

  • 200 OK: Webhook deleted successfully.
  • 404 Not Found: Webhook not found.
  • 500 Internal Server Error: Error deleting the webhook.

Webhook Payload

When a webhook is triggered, it will POST the tag's unique identifier, the event type, a timestamp, and the most recent known position.

Request Body:

{
    "eventId": "event-id",
    "eventType": "enter_geofence",
    "timestamp": "2025-09-19T12:07:25.420Z",
    "data": {
        "tagId": "tag-id",
        "position": {
            "x": 1.3370211919216917,
            "y": 7.778763176697338
        },
        "geofence": {
            "id": 87,
            "name": "geofence-name"
        }
    }
}

Analytics

GET /analytics/tags

Aggregates tag movement and geofence dwell time over a specified period. The report calculates the total distance traveled by each tag and the time (in minutes) spent within each geofence.

Query Parameters:

  • startDate (required) - The start date and time for the report in ISO 8601 format (e.g., 2025-09-19T00:00:00Z).
  • endDate (required) - The end date and time for the report in ISO 8601 format (e.g., 2025-09-20T00:00:00Z).

Response Body:

{
    "reportPeriod": {
        "start": "2025-09-19T00:00:00.000Z",
        "end": "2025-09-20T00:00:00.000Z"
    },
    "tags": {
        "TAG_ID_1": {
            "totalDistance": 150.75,
            "geofenceDwellTime": {
                "Warehouse Zone A": 45.5,
                "Loading Bay": 12.3
            }
        },
        "TAG_ID_2": {
            "totalDistance": 88.2,
            "geofenceDwellTime": {
                "Office Area": 120.0
            }
        }
    }
}

Responses:

  • 200 OK: Returns the analytics report.
  • 400 Bad Request: Missing or invalid startDate or endDate.
  • 500 Internal Server Error: Failed to generate the report.

GET /analytics/events

Generates a log of discrete events, such as geofence entries and exits, based on tag history within a specified time frame. This endpoint dynamically creates events rather than reading from a static log.

Query Parameters:

  • startDate (required) - The start date for the report in ISO 8601 format (e.g., 2025-09-19T00:00:00Z).
  • endDate (required) - The end date for the report in ISO 8601 format (e.g., 2025-09-20T00:00:00Z).
  • event_type (optional) - Filter events by type (e.g., enter_geofence, exit_geofence).
  • tag_id (optional) - Filter events for a specific tag.
  • geofence_id (optional) - Filter events related to a specific geofence.

Response Body:

{
    "reportPeriod": {
        "start": "2025-09-19T00:00:00.000Z",
        "end": "2025-09-20T00:00:00.000Z"
    },
    "events": [
        {
            "id": 1,
            "timestamp": "2025-09-19T10:30:00Z",
            "eventType": "exit_geofence",
            "tagId": "TAG_ID_1",
            "details": {
                "geofenceId": 19,
                "geofenceName": "Warehouse Zone A"
            }
        },
        {
            "id": 2,
            "timestamp": "2025-09-19T10:30:00Z",
            "eventType": "enter_geofence",
            "tagId": "TAG_ID_1",
            "details": {
                "geofenceId": 20,
                "geofenceName": "Loading Bay"
            }
        }
    ]
}

Responses:

  • 200 OK: Returns the generated event log.
  • 400 Bad Request: Missing or invalid startDate or endDate.
  • 500 Internal Server Error: Failed to generate the event log.

GET /analytics/geofences

Provides a summary of geofence activity, including total visits, unique visitors, and dwell times over a specified period.

Query Parameters:

  • startDate (required) - The start date and time for the report in ISO 8601 format (e.g., 2025-09-19T00:00:00Z).
  • endDate (required) - The end date and time for the report in ISO 8601 format (e.g., 2025-09-20T00:00:00Z).

Response Body:

{
    "reportPeriod": {
        "start": "2025-09-19T00:00:00.000Z",
        "end": "2025-09-20T00:00:00.000Z"
    },
    "geofences": [
        {
            "geofenceId": 19,
            "geofenceName": "Warehouse Zone A",
            "totalVisits": 15,
            "uniqueVisitors": 4,
            "totalDwellTime": 250.75,
            "averageDwellTime": 16.72
        },
        {
            "geofenceId": 20,
            "geofenceName": "Loading Bay",
            "totalVisits": 32,
            "uniqueVisitors": 6,
            "totalDwellTime": 180.5,
            "averageDwellTime": 5.64
        }
    ]
}

Responses:

  • 200 OK: Returns the geofence summary report.
  • 400 Bad Request: Missing or invalid startDate or endDate.
  • 500 Internal Server Error: Failed to generate the report.

GET /analytics/heatmap

Generates a collection of coordinate points from tag history to create a heatmap visualization of device activity.

Query Parameters:

  • startDate (required) - The start date for the data range (e.g., 2025-09-19T00:00:00Z).
  • endDate (required) - The end date for the data range (e.g., 2025-09-20T00:00:00Z).
  • tag_id (optional) - Filter heatmap data for a specific tag.
  • geofence_id (optional) - Filter heatmap data for a specific geofence.

Response Body:

{
    "reportPeriod": {
        "start": "2025-09-19T00:00:00.000Z",
        "end": "2025-09-20T00:00:00.000Z"
    },
    "points": [
        { "x": 5.25, "y": 8.10 },
        { "x": 5.20, "y": 8.05 },
        { "x": 5.18, "y": 8.02 },
        ...
    ]
}

Responses:

  • 200 OK: Returns an array of coordinate points.
  • 400 Bad Request: Missing or invalid startDate or endDate.
  • 500 Internal Server Error: Failed to generate heatmap data.

GET /analytics/proximity

Analyzes how many times other tags were detected within a specified radius of a primary tag during a given time period.

Query Parameters:

  • tag_id (required) - The primary tag to analyze proximity against.
  • startDate (required) - The start date for the analysis (e.g., 2025-09-19T00:00:00Z).
  • endDate (required) - The end date for the analysis (e.g., 2025-09-20T00:00:00Z).
  • radius (optional) - The proximity radius in meters. Defaults to 2.0.

Response Body:

{
    "reportPeriod": {
        "start": "2025-09-19T00:00:00.000Z",
        "end": "2025-09-20T00:00:00.000Z"
    },
    "primaryTagId": "TAG_A",
    "proximityRadius": 2,
    "proximateTags": [
        {
            "tagId": "TAG_B",
            "proximityCount": 125
        },
        {
            "tagId": "TAG_C",
            "proximityCount": 88
        }
    ]
}

Responses:

  • 200 OK: Returns the proximity analysis report.
  • 400 Bad Request: Missing required parameters or invalid date format.
  • 500 Internal Server Error: Failed to perform proximity analysis.

Admin

POST /admin/restart

Signals the worker process to gracefully shut down and restart the system.

Authentication:

Requires YOUR_ADMIN_TOKEN.

Responses:

  • 202 Accepted: Restart signal sent successfully. The worker will process it shortly.
  • 403 Forbidden: Invalid or missing admin API key.
  • 500 Internal Server Error: Failed to create the restart signal file.