Vision Block
A typed pattern for plugging any spatial-perception stack — vision, SLAM, LiDAR, depth, IMU, multimodal — into one multi-scale time axis. You handle the where; we handle the when, anchored to an authoritative time source (NIST / NTP) rather than the host's local wall clock.
POST /v1/spiral/batch is a Standard+ feature. Lite keys get HTTP 402 VISION_BLOCK_REQUIRES_PAID_TIER. Paid tiers are rate-limited, not metered: Standard caps at 100,000 stamps/minute, Pro at 500,000 stamps/minute, Enterprise is uncapped by default. Calls that exceed the per-minute ceiling return HTTP 429 VISION_BATCH_THROTTLED with a numeric Retry-After — no PAYG surprise.
POST /v1/spiral/batch widget live in the customer dashboard.Sign in to view full reference →Every observation a perception stack emits — a point-cloud sweep, a video frame, a depth map, an IMU sample — has a spatial address (x, y, z) from your sensor and a temporal address from the clock. Vision Block turns the temporal half into one HTTP call (or one typed SDK method) regardless of which sensor produced the observation. The result is a fused 4D address (x, y, z, θ) where θ is the patent-pending Temporal Spiral — a twelve-scale phase coordinate that lets you slice, fuse, or replay observations by any timescale: control loop, mission day, orbital period, regulatory year.
For high-rate producers, the streaming surface stampObservationsStream() holds one persistent WebSocket and now survives flaky links transparently: transient disconnects (WiFi handoff, cellular reconnect, container restart) trigger exponential-backoff auto-reconnect, queued sends are replayed across the gap, and consumers only see a reconnect sentinel when the reference anchor itself shifted enough to matter.
Give your A.I. sight — with the VLM of your choice
Vision Block does not host a vision-language model. You bring the VLM — DeepSeek-VL, Qwen-VL, GPT-4o, Gemini, Moondream, anything that takes an image and returns text — and Vision Block attaches a Spiral-anchored temporal coordinate to every observation. That keeps model choice, inference cost, and data residency in your hands, while you still get the twelve-scale phase coordinate fused onto each frame.
Stamp at capture, not after inference. The wall-clock anchor is NTP-class (~10–50 ms against true UTC), so a VLM round-trip of 200–2000 ms would otherwise dominate your timing error. Call tb.stampObservations() immediately when the frame arrives off the sensor, then run the VLM, then associate its output with the already-stamped observation.
Worked example — DeepSeek-VL via its public chat-completions endpoint:
import { TemporalBlock } from "temporalblock";
const tb = new TemporalBlock({ apiKey: process.env.TBLK_KEY! });
async function seeFrame(frameJpegBase64: string, frameId: string) {
// 1. Stamp at capture time — BEFORE the VLM round-trip.
const stamped = await tb.stampObservations([
{ t: Date.now(), payload: { frameId } },
]);
const obs = stamped.observations[0];
// 2. Call your VLM of choice. DeepSeek-VL shown here; the
// shape is identical for any OpenAI-compatible VLM.
const vlm = await fetch("https://api.deepseek.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.DEEPSEEK_API_KEY!}`,
},
body: JSON.stringify({
model: "deepseek-vl",
messages: [{
role: "user",
content: [
{ type: "text", text: "Describe what you see in one sentence." },
{ type: "image_url", image_url: { url: `data:image/jpeg;base64,${frameJpegBase64}` } },
],
}],
}),
}).then((r) => r.json());
// 3. Return the VLM caption joined to the spiral coordinate.
return {
frameId,
caption: vlm.choices[0].message.content,
spiral: obs.spiral, // twelve-scale phase, anchored to NIST
};
}Swap the model. The same shape works with any VLM endpoint — replace the fetch URL, headers, and request body with whatever your VLM expects. Vision Block is unchanged either way:
// Generic VLM — Qwen-VL, GPT-4o, Gemini, Moondream, self-hosted, …
const stamped = await tb.stampObservations([{ t: Date.now(), payload: { frameId } }]);
const vlm = await callYourVlmHere(frameJpegBase64);
return { frameId, caption: vlm.text, spiral: stamped.observations[0].spiral };One-call shorthand. If your pipeline is the canonical "single frame in, captioned observation out" shape, the SDK ships tb.seeFrame(frame, vlmCallback) — same stamp-before-VLM ordering, same BYO positioning, no extra metering. Pass your frame plus a callback that calls your VLM and the helper returns the frame, the model output, and the spiral coordinate joined in one object:
const result = await tb.seeFrame(
{ id: frameId, jpegBase64: frameJpegBase64 },
async (frame, stamp) => {
const vlm = await callYourVlmHere(frame.jpegBase64);
return { caption: vlm.text };
},
);
// result.frame, result.vlmResult.caption, result.spiralFor production robotics deployments where the frames are already flowing through ROS 2 topics, the ros2-temporalblock adapter below stamps everysensor_msgs/* message at the subscriber edge — no per-frame application code at all. The BYO-VLM recipe above is the right starting point for one-off pipelines and non-ROS stacks.
Drop-in ROS 2 adapter
A reference ament_python package ships in the repo at integrations/ros2-temporalblock/. It registers one node (spiral_stamper) that subscribes to any sensor_msgs/* topic, batches each message's header.stamp to POST /v1/spiral/batch, and republishes on a sibling <topic>/spiral with the full twelve-scale phase coordinate attached as a sidecar JSON envelope.
Two lines plus a launch file — no publisher-side code changes:
Node(package="ros2_temporalblock", executable="spiral_stamper",
parameters=[{"api_key": "tblk_live_...",
"topics": ["/scan|sensor_msgs/msg/LaserScan"]}])Full integration guide → integrations/ros2-temporalblock/README.md
Anchoring the global rung to your session
By default the outermost ("global") rung of the spiral is anchored to the Unix epoch with a base period of one Julian year (≈31,557,600,000 ms), so the same wall-clock instant always maps to the same global phase regardless of who stamped it. That is what you want for fleet-wide replay and cross-tenant comparisons. For a single SLAM run, drone flight, or robot mission, you usually want the global rung to instead start at your session boundary so that phase 0 lines up with t=0 of the mission and the period matches the natural cadence of the work (one orbit, one sortie, one shift). Pass globalOrigin (UTC ms since Unix epoch) and globalBasePeriodMs on the request to override the defaults — every stamp in that call is then phased against your mission clock instead of the calendar.
// Mission started at 2026-05-24T14:00:00Z; phase by the hour.
const missionStartMs = Date.parse("2026-05-24T14:00:00Z");
const oneHourMs = 60 * 60 * 1000;
await fetch("https://api.temporalblock.com/api/v1/spiral/batch", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.TBLK_KEY!}`,
},
body: JSON.stringify({
globalOrigin: missionStartMs,
globalBasePeriodMs: oneHourMs,
observations: [
{ t: Date.now(), payload: { frameId: "f-0001" } },
{ t: Date.now() + 33, payload: { frameId: "f-0002" } },
],
}),
});Validation. globalBasePeriodMs is silently clamped into [1 ms, 1e15 ms]; non-finite or non-positive values return HTTP 400 INVALID_GLOBAL_BASE_PERIOD_MS. A non-finite globalOrigin returns HTTP 400 INVALID_GLOBAL_ORIGIN. Omit both fields and you get the Unix-epoch / Julian-year default — the right choice when you need stamps to be comparable across tenants or sessions.
The temporal anchor is NTP-class by default — ~10–50 ms against true UTC, which covers fleet coordination, mission planning, regulatory replay, and most robotics control loops. For tight LiDAR-IMU fusion above 100 Hz, BYO an IEEE 1588 grandmaster on a dedicated deployment and the shipped PTP adapter settles at ~100 µs on a quiet LAN in pure-userland mode. Dedicated deployments that ship the optional ptp-hwstamp NIC-stamping addon advertise sub-microsecond on a healthy grandmaster link. Full precision walkthrough → dashboard
Temporal Spiral is patent pending — U.S. Provisional Application No. 64/065,213 (filed 2026-05-14).