Documentation

Telemetry Station for AI Agents

Estimate costs, prepare an activation, and retrieve telemetry through a REST API or an MCP server. Prices are in USD and credits (1 USD = 1 credit). All dates are UTC. Payment, email codes, and passwords stay with the customer.

1 · Quick start

Estimate a deployment in one request.

No authentication is needed for public operations. The response lists plans from lowest to highest total, each with a pre-filled link for the customer.

curl -s https://telemetrystation.com/api/agent/v1/estimates \
  -H 'Content-Type: application/json' \
  -d '{
    "network": "sbd",
    "deviceCount": 12,
    "messageBytes": 45,
    "transmitIntervalMinutes": 15,
    "startDate": "2026-09-01",
    "endDate": "2027-03-01"
  }'
2 · Human approvals

What only the customer can do.

An agent never sees one-time codes, pays, or sets passwords. Give the customer the handoff link and tell them these steps remain.

  • customer details and email otp: The customer enters their details on the Telemetry Station website and types the one-time code emailed to them. Agents never see or relay the code.
  • imei registration: IMEIs are verified by Telemetry Station against the Iridium network during activation. Local format checks from this API are not authoritative.
  • destinations: Optional email, DirectIP, or HTTPS destinations are configured in the activation flow.
  • payment approval: The customer reviews the final amount and pays through Stripe Checkout. Agents cannot pay or approve payment.
  • account password: The customer creates their own account password. Agents never set or receive passwords.
3 · Authentication

Public operations and the customer API key.

Estimates, modems, requirements, and inquiries are public and rate limited. Telemetry retrieval requires the customer's data API key (the apiKey value in the export URL from Device actions in their dashboard) as Authorization: Bearer <key>. Keys are rejected in URLs and request bodies. The key grants read-only telemetry access and cannot pay or change devices.

4 · REST API

Endpoints under /api/agent/v1.

GET/capabilitiesCapabilities, limits, and human checkpoints.
GET/modemsSupported Iridium modems and message-size limits.
POST/estimatesPrice a deployment with the real pricing engine. Creates no charge.
POST/imei-validationsLocal IMEI format and check-digit validation. Not authoritative.
GET/activation-requirementsWhat the customer must do to activate.
POST/activation-preparationsValidate a plan and get a pre-filled customer link. Does not activate or charge.
GET/custom-solutionsCustom engineering, BEAR Module, and partnership options.
POST/inquiry-draftsValidate an inquiry. Sends nothing.
POST/inquiriesSend an inquiry. Requires confirmedByCustomer: true.
GET/telemetry/messagesRetrieve messages with the customer's API key (Bearer).

Responses are { data, requestId }. Errors are { error: { code, message, retryable, details, nextAction }, requestId }. Responses are never cached.

5 · MCP

Connect an MCP client.

Remote MCP over Streamable HTTP at https://telemetrystation.com/mcp. Sessions are stateless. Public tools need no authentication; supply the API key as a header for telemetry.

{
  "mcpServers": {
    "telemetry-station": {
      "url": "https://telemetrystation.com/mcp",
      "headers": { "Authorization": "Bearer <TELEMETRY_STATION_API_KEY>" }
    }
  }
}

Tools: get_capabilities, list_modems, estimate_deployment, validate_imeis, get_activation_requirements, prepare_activation, get_telemetry_messages, list_custom_solutions, draft_inquiry, submit_inquiry (requires customer confirmation). Resources and prompts are also provided. Tool annotations declare read-only behavior, and the only tool that sends anything requires confirmedByCustomer: true.

6 · Telemetry

Retrieve data on an hourly schedule.

Windows are half-open UTC intervals [start, end) of at most 31 days, using dates as YYYY-MM-DD. Request from the last processed UTC day to tomorrow, then de-duplicate. Records are returned exactly as the data service sends them, in data.records. Inspect a real response before depending on specific fields.

import hashlib, json, os, time
from datetime import datetime, timedelta, timezone
import requests

BASE = "https://telemetrystation.com/api/agent/v1"
KEY = os.environ["TELEMETRY_STATION_API_KEY"]  # customer data API key; never hard-code
IMEIS = "300000000000001,300000000000002"       # your devices, comma-separated

def fetch(start: str, end: str):
    for attempt in range(5):
        r = requests.get(
            f"{BASE}/telemetry/messages",
            params={"imeis": IMEIS, "start": start, "end": end},
            headers={"Authorization": f"Bearer {KEY}"},
            timeout=30,
        )
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "10")))
        elif r.status_code >= 500:
            time.sleep(2 ** attempt)
        else:
            r.raise_for_status()
            return r.json()["data"]["records"]
    raise RuntimeError("Telemetry Station unavailable; retry the same window later")

def run_hourly(last_processed_day, seen):
    today = datetime.now(timezone.utc).date()
    start = last_processed_day or today.isoformat()
    end = (today + timedelta(days=1)).isoformat()  # window is [start, end)
    for record in fetch(start, end):
        # Records are passed through unchanged. De-duplicate on the whole record.
        fingerprint = hashlib.sha256(json.dumps(record, sort_keys=True).encode()).hexdigest()
        if fingerprint not in seen:
            seen.add(fingerprint)
            store(record)  # your storage
    return today.isoformat()  # persist as last_processed_day
const BASE = "https://telemetrystation.com/api/agent/v1";
const KEY = process.env.TELEMETRY_STATION_API_KEY!;
const IMEIS = ["300000000000001", "300000000000002"];

async function fetchRecords(start: string, end: string) {
  for (let attempt = 0; attempt < 5; attempt++) {
    const url = new URL(BASE + "/telemetry/messages");
    url.search = new URLSearchParams({ imeis: IMEIS.join(","), start, end }).toString();
    const res = await fetch(url, { headers: { Authorization: "Bearer " + KEY } });
    if (res.status === 429) {
      await new Promise((r) => setTimeout(r, Number(res.headers.get("Retry-After") ?? 10) * 1000));
    } else if (res.status >= 500) {
      await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
    } else if (!res.ok) {
      throw new Error(JSON.stringify(await res.json()));
    } else {
      return (await res.json()).data.records as unknown[];
    }
  }
  throw new Error("Telemetry Station unavailable; retry the same window later");
}

// Run hourly. Window is [start, end) in UTC. Persist lastProcessedDay between runs.
export async function runHourly(lastProcessedDay: string | undefined, seen: Set<string>) {
  const today = new Date().toISOString().slice(0, 10);
  const end = new Date(Date.parse(today) + 86_400_000).toISOString().slice(0, 10);
  for (const record of await fetchRecords(lastProcessedDay ?? today, end)) {
    const fingerprint = JSON.stringify(record); // records are passed through unchanged
    if (!seen.has(fingerprint)) { seen.add(fingerprint); /* store(record) */ }
  }
  return today;
}
7 · Limits

Rate limits, retries, and known gaps.

Default: 60 requests per minute per client IP. Exceeding it returns HTTP 429 with Retry-After; RateLimit-* headers are on every response. Retry only when error.retryable is true.

  • Agents cannot pay, enter email one-time codes, or create passwords. Those steps are intentionally human-only.
  • There is no delegated or OAuth agent token yet, so an agent cannot read activation or checkout status on its own.
  • Listing a customer's devices needs a customer login session, so you supply the IMEIs.
  • The telemetry data endpoint has no cursor, so use UTC date windows and de-duplicate.
  • Rate limits and Idempotency-Key replay are per server instance. Treat them as best effort.