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:
- Created directly here, mapping an existing Process output to a URL.
- Created automatically when you toggle Read access on an HTTP endpoint in Connect. The toggle materialises a passthrough output that mirrors the raw payload (see Available JSONPath variables).
Endpoints
Each endpoint maps one output to a URL. Creating one requires:
- Output — the transformation output from Process that this endpoint serves.
- Auth methods — which credentials the public URL accepts.
oauth2only (the default) requires a workspace-bound Bearer token;oauth2 + nonealso accepts anonymous callers. - Required scope — optionally require a scope on authenticated callers. Only meaningful when
noneis not in Auth methods; the two cannot be combined.
Creating an endpoint
- Click + New endpoint.
- Select the output to expose.
- Pick Auth methods: keep the default
oauth2to require a Bearer token, or addnoneto make the URL publicly readable. - Optionally select a Required scope from the scopes defined in Access. If set, authenticated callers must present a Bearer token carrying this scope.
- 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>| Parameter | Description |
|---|---|
from | Start of time range (Unix timestamp). Optional. |
to | End of time range (Unix timestamp). Optional. |
limit | Records 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.
- Enter one or more origins separated by commas (e.g.
https://mydashboard.example.com), or*to allow all origins (not recommended for production). - 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:
| Variable | Type | Notes |
|---|---|---|
$received_at | timestamptz | The row's ingestion timestamp. Use directly as a JSON value, or $received_at.datetime() for date arithmetic. |
$metadata | jsonb | Per-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
- Expand a Processed endpoint row and click + Add webhook.
- Enter a Name (shown only in the dashboard) and a Delivery URL.
- 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... }| Header | Purpose |
|---|---|
X-IotMan-Event-Id | Stable identifier for the underlying sensor event. Use this to deduplicate — see delivery guarantees below. |
X-IotMan-Timestamp | Unix seconds when IoTMan signed the request. Reject if more than 5 minutes skewed from your clock to prevent replay. |
X-IotMan-Signature | sha256=<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
- At-least-once. A successful 2xx response is considered delivered. Anything else is retried.
- Retry schedule. 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours. After the final failed attempt the delivery is marked dead and shown in the dashboard for manual retry.
- Duplicate events are possible. Network failures or IoTMan restarts can cause the same event to be delivered more than once. Always dedupe on
X-IotMan-Event-Id— it is stable per underlying sensor payload. - Circuit breaker. If a webhook URL returns non-2xx many times in a row, IoTMan pauses delivery to that webhook until you re-enable it.
- Ordering is not guaranteed. Retries can overtake fresh events. If ordering matters, sort by
X-IotMan-Timestampon receipt.
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>/eventsFrame 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>/streamIt 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.