Skip to Content
ConceptsAdapters

Adapters: bring your own data connectors

Available

  • series, weather and events data adapters: CRUD, encrypted env, egress allow-list, POST /v1/adapters/{id}/test, flow runs with dataSource: adapter
  • targets adapters: your source catalog, synced into the Engine hourly and on demand
  • comms adapters: your own email, SMS or voice sender for alerts and reports

Planned

  • Python adapters
  • Run logs
  • Auto-disable on repeated failure
  • Per-tenant quotas

The Engine stores no sensor, weather or event data. When a Flow runs, the Engine asks your adapter for exactly the window it needs, uses the data for that run, and discards it, so your systems stay the single source of truth. An adapter is a small script, stored in the Engine, that the Engine executes (sandboxed) whenever a Flow needs that facet of data.

An adapter is:

PartWhat it is
Facetwhat it does. Data in: series (sensor readings), weather (forecasts), events (scout events); catalog: targets (supplies your source list, synced into the Engine); data out: comms (sends alert/report messages through your own provider); one adapter per facet
Scriptyour code: TypeScript / JavaScript today (Python is planned), one default-exported function
Enva key/value set of environment variables (API tokens, base URLs…). Values are encrypted at rest and never returned after you write them; your script sees them as ctx.env
Egress allow-listthe hostnames your script may call; nothing else is reachable

Scripts run in the same Worker sandbox as tiles: there is no filesystem or environment, and no network except the hosts on your allow-list (an empty list means no network at all). Time and output size are capped; run logs, auto-disable on repeated failure and per-tenant quotas are planned. The last error is kept on the adapter (lastError).

Script contract

// facet: series. Canonical metric names (see the metric dictionary), epochs in milliseconds export default async function (ctx, req: { sourceIds: string[]; metrics: string[]; startMs: number; endMs: number }) { const res = await ctx.fetch(`${ctx.env.BASE_URL}/telemetry?from=${req.startMs}&to=${req.endMs}`, { headers: { Authorization: `Bearer ${ctx.env.TOKEN}` }, }); const rows = await res.json(); return { series: { "temperature.ambient": rows.map((r) => [r.ts, r.temp]) } }; }

Per facet: series gets {sourceIds, sourceKeys, metrics, startMs, endMs, userKey} and returns {series: {<metric>: [[ts, value], …]}} · weather gets {lat, lng, startMs, endMs, userKey} and returns {days: […]} (the standard weather shape, ms epochs) · events gets {sourceIds, sourceKeys, startMs, endMs, userKey} and returns {events: [{ts, key, value?}, …]}. userKey is the tenantKey of the user behind the run (or null for key-initiated runs); use it when your API needs a user identity. sourceKeys parallels sourceIds with each source’s tenantKey (your own id for it, such as a device serial or a field id), falling back to the Engine id when unset. Key your upstream lookups on it and register sources with the tenantKey your system knows. ctx provides env, the egress-restricted fetch, log(level, message) and now. Results are shape-checked by the Engine; an invalid or over-size result fails the run with a clear error.

The targets facet supplies your source catalog instead of run data: the Engine calls it with {userKey} (null on scheduled syncs; put your account identity in the adapter env) and it returns {sources: [{key, name, hint?, sensorType?, location?, boundary?, timezone?, metadata?}]}. key is your stable id for the source and becomes its tenantKey (exactly what your series/events adapters receive back as sourceKeys). The Engine reconciles the list into /v1/sources hourly and on POST /v1/adapters/{id}/sync: new keys are created (marked metadata.managedBy: "targets-adapter"), matching hand-registered sources are adopted, managed sources missing from your list are disabled, never deleted, and re-enabled if they return. Sources you registered by hand without a matching key are left alone.

The outbound comms facet inverts the direction: the Engine calls it to send a message instead of fetch data. It gets {channel: "email" | "sms" | "voice", to, subject?, message, html?} and returns {ok: true} or {ok: false, error}. While a comms adapter is active, all alert and report deliveries for your tenant go through it (per message: a failed send is recorded against that recipient); disable or delete it to fall back to the platform transports.

Choosing what a Flow uses

Each data tile carries a Data source parameter: generate (deterministic demo data, so you can build and test flows before your data is connected) or adapter (your adapter for that facet). All tiles that read the same facet in one flow must agree; a run that needs a facet in adapter mode without an active adapter fails with a message pointing here.

The Growth Lab GUI exposes this as Settings → Adapters when you sign in as the tenant (not as a grower). Env values are write-only; Test and (for targets) Sync sources are on the same pane. Grower sessions cannot list or edit adapters.

Sample adapters

Four complete adapters for the Soiltech platform API are available from Soiltech on request, together with a small registration script that installs any adapter through /v1/adapters:

  • weather: forecasts via an upstream weather proxy (auth headers from ctx.env, second→ms epochs, upstream-error handling, userKey identity)
  • series: sensor readings (sourceKeys = device serials, wire-field → canonical-metric mapping, cumulative-rain handling, window clamping)
  • events: scout events (sourceKeys = field ids, per-id fan-out with bounded concurrency, ISO→ms timestamps, lower-cased keys)
  • targets: catalog sync (fields → areas with boundary rings, devices → sensors with locations, keyed by field id / device serial so the data adapters get the right upstream ids automatically)

Endpoints (Available)

POST/GET/PATCH/DELETE /v1/adapters[/{id}] (one adapter per facet; env is write-only; responses return envKeys only; PATCH status: disabled to turn one off) · POST /v1/adapters/{id}/test (run once with a sample request, returns the result or error). Both: tenant gate, scope adapters:manage. Planned: GET /v1/adapters/{id}/runs, adapter.disabled webhook. Outbound alerts and webhooks remain Engine-managed.