Export

Serve your workspace data over a queryable REST API, a live SSE stream, or outbound webhooks.

The Export tab lists every read-accessible endpoint in your workspace. Each endpoint maps one transformation output to a URL exposed for polled GET access, Server-Sent Events streaming at /events, and outbound webhook delivery on each match.

Endpoints land on this tab in two ways:

Endpoints

Each endpoint maps one output to a URL. Creating one requires:

Creating an endpoint

  1. Click + New endpoint.
  2. Select the output to expose.
  3. Pick Auth methods: keep the default oauth2 to require a Bearer token, or add none to make the URL publicly readable.
  4. Optionally select a Required scope from the scopes defined in Access. If set, authenticated callers must present a Bearer token carrying this scope.
  5. Click Create. The endpoint URL is shown immediately.

Requires Editor or Admin role.

Removing an endpoint

Click the delete button. The URL stops responding immediately. The underlying output and data are not affected.

Requires Editor or Admin role.

Querying an endpoint

For endpoints with none in Auth methods, no credentials are needed. Otherwise, include a Bearer token (any workspace-bound token when no scope is required, or one carrying the required scope):

GET <endpoint-url>?from=<ts>&to=<ts>&limit=<n>
Authorization: Bearer <token>

Tokens are issued by IoTMan's OAuth2 token endpoint. See the Authorization guide for how to obtain one.

GET <endpoint-url>?from=<ts>&to=<ts>&limit=<n>
ParameterDescription
fromStart of time range (Unix timestamp). Optional.
toEnd of time range (Unix timestamp). Optional.
limitRecords per page. Default 100.

Response:

{
  "data": [ { ...output-shape... }, ... ],
  "next_page": "<endpoint-url>?to=<ts>&limit=<n>"
}

next_page is a ready-to-use URL for the next page of older records. It is null when there are no more results.

Each item in data has the shape defined by the JSON constructor in the output's transformation rule.

CORS origins

To allow browsers to query your data API directly (e.g. from a Grafana panel or a custom dashboard), add the permitted origins to the CORS allowlist.

  1. Enter one or more origins separated by commas (e.g. https://mydashboard.example.com), or * to allow all origins (not recommended for production).
  2. Click Save.

Requires Editor or Admin role.

Available JSONPath variables

When the constructor for an output evaluates against a sensor row, IoTMan binds two JSONPath named variables on every call:

VariableTypeNotes
$received_attimestamptzThe row's ingestion timestamp. Use directly as a JSON value, or $received_at.datetime() for date arithmetic.
$metadatajsonbPer-row metadata stamped at ingest. Subscriptable: $metadata.source_ip, $metadata.auth_method, $metadata.oauth2_client_id, $metadata.ingest_context.<…>, etc.

Both are real PostgreSQL JSONPath named variables, not template sigils — they parse in any JSONPath tool that supports vars, and you can use them inside filter expressions:

$.value ? ($metadata.auth_method == "oauth2")

The "Read access" toggle on Connect uses them in the auto-generated passthrough constructor:

{ "json_data": "$", "received_at": "$received_at", "metadata": "$metadata" }

Webhooks

A webhook is an outbound HTTP POST that IoTMan sends to a URL you control, every time a new sensor payload matches the transformation rule behind an endpoint. The body is the same shape as one item of that endpoint's data array — exactly what a GET on the endpoint would return — so you can process a push event and a pull response with the same code.

Creating a webhook

  1. Expand a Processed endpoint row and click + Add webhook.
  2. Enter a Name (shown only in the dashboard) and a Delivery URL.
  3. Click Create. The signing secret is generated server-side and shown alongside the webhook in the dashboard so you can copy it whenever the receiver needs it. To rotate, delete the webhook and create a new one.

Requires Editor or Admin role.

Request format

POST <your-delivery-url>
Content-Type: application/json
X-IotMan-Event-Id: <uuid>
X-IotMan-Timestamp: <unix seconds>
X-IotMan-Signature: sha256=<hex>

{ ...output-shape... }
HeaderPurpose
X-IotMan-Event-IdStable identifier for the underlying sensor event. Use this to deduplicate — see delivery guarantees below.
X-IotMan-TimestampUnix seconds when IoTMan signed the request. Reject if more than 5 minutes skewed from your clock to prevent replay.
X-IotMan-Signaturesha256=<hex> HMAC-SHA256 of <timestamp>.<raw-body> using the signing secret. Verify with a constant-time comparison.

Verifying a signature

Python:

import hmac, hashlib

def verify(secret: str, timestamp: str, body: bytes, signature_header: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.".encode() + body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature_header)

Delivery guarantees

Removing a webhook

Click the delete button on the webhook row. Delivery stops immediately. Any pending or dead deliveries are discarded.

Requires Editor or Admin role.

Live stream (Server-Sent Events)

Every endpoint exposes an SSE stream for real-time delivery at:

https://iotman.io/data/<workspace-short-id>/<url-path>/events

Frame shape

One SSE data: frame per sensor row that matches the output's transformation rule, already transformed by the output's constructor. The frame is the bare transformed object — the same shape as one item in a polled GET's data[] and the same body a webhook POSTs:

data: { ...output-shape... }

If you need a timestamp or identifier on each frame, reference $received_at or $metadata inside the output's constructor (see Available JSONPath variables). The auto-generated passthrough used by the Connect-tab read toggle already does this — its frames look like { "json_data": {...}, "received_at": "...", "metadata": {...} }.

A keep-alive comment fires every 20 seconds so idle connections survive proxies and load balancers.

Authentication

For endpoints with none in Auth methods, no credentials are needed — the stream starts immediately on connect. Native browser EventSource works directly.

Otherwise, send a Bearer token whose aud matches the workspace slug. When a required scope is set, the token's scope claim must contain it:

Authorization: Bearer <JWT>

Native browser EventSource cannot set custom headers — for browser callers, use the eventsource npm package (a fetch-based polyfill) which accepts headers. Non-browser clients (Node, Python, curl, Go) set the header trivially.

Delivery semantics

The stream is live-only: a client sees events from its connection forward. There is no historical replay and no retry on lag — a subscriber that can't keep up with the broadcast rate will drop intermediate frames. For history, use the polled GET. For guaranteed delivery with retries, use a webhook.

WebSocket stream (legacy)

A WebSocket sibling at /stream remains for existing clients:

wss://iotman.io/data/<workspace-short-id>/<url-path>/stream

It carries the same frames as the SSE stream above. Protected endpoints authenticate with a first-message handshake — within 5 seconds of the connection opening, send { "type": "auth", "token": "<JWT>" }; the server replies { "type": "authorized" } and starts streaming. On invalid or missing token, or timeout, the server closes with code 1008.

This path is deprecated: the WS upgrade response carries Deprecation: true (RFC 8594) and the AsyncAPI spec marks the channel x-iotman-deprecated: true with a successor pointing at /events. New integrations should use SSE. The WebSocket endpoint will be removed in a future release.