Developer guide

temporalBLOCK Guide

Every feature — from first API call to advanced Spiral integration — with working code examples for both novice and advanced users.


Step 0 · Novice

Authentication

Every request requires an X-API-Key header. Get your key from the dashboard. Keys follow the format tblk_(live|test)_…. The key goes in the X-API-Key header — not Authorization: Bearer. Sending it as a Bearer token returns a 401 with code WRONG_AUTH_HEADER. A missing or invalid key returns 401 INVALID_API_KEY.

// All requests — add this header
fetch("https://api.temporalblock.com/api/v1/calibrate", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": process.env.TBLK_API_KEY,  // never hardcode
  },
  body: JSON.stringify({ timezone: "America/New_York" }),
})
TierPriceSkills rowsIncluded calls / mo
Lite$9read-onlycalibrate + bridge only
Standard$2410 activesnippet / briefing included
Pro$49100 active1,500 snippet · 1,500 briefing · 150 deep
Enterprisecustomunlimitednegotiated

PAYG past included quota: snippet $0.02 · briefing $0.04 · deep $0.10.


Step 1 · Novice · Most important call

Calibrate

POST /api/v1/calibrate returns a prompt block that anchors your LLM to the certified current date, time, and the boundary of its training data. Call this before every conversation that involves any time-sensitive question.

⚠ Always pass the user's timezone. If you omit it, the block contains the UTC hour. The LLM will report that as the user's local time — which can be off by many hours. Always detect and forward the timezone:
// Detect timezone in the browser or React Native
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
// e.g. "America/New_York", "America/Chicago", "America/Los_Angeles",
//      "America/Denver", "Europe/London", "Asia/Tokyo"

const { calibrateBlock } = await fetch(
  "https://api.temporalblock.com/api/v1/calibrate",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": process.env.TBLK_API_KEY,
    },
    body: JSON.stringify({ timezone }),
  }
).then((r) => r.json());

// Prepend calibrateBlock to your system prompt — it's a ready-made string
const systemPrompt = calibrateBlock + "\n\n" + yourExistingSystemPrompt;
What calibrateBlock contains: the certified current date and time (in the user's local timezone), the model's knowledge boundary date, and a brief reconciliation note. Prepend it to the system prompt — do not summarise or paraphrase it.

Additional options:

  • clientTime — user's self-reported local time (ISO string). Helps the block confirm against the server anchor.
  • model — the LLM model name (e.g. "claude-opus-4-5") so the block can name the correct knowledge boundary.
  • If the user's timezone is unknown, pass timezone: "UTC" — the block labels it UTC so the LLM doesn't misrepresent it as local time.

Step 2 · Novice / Intermediate

Bridge & Full

Bridge reconciles your model's knowledge with the live web. POST /api/v1/bridge runs a real-time search on your behalf and returns a reconciliation block. POST /api/v1/full combines calibrate + bridge into one round trip — the most common choice.

POST /api/v1/full — recommended starting point

// /v1/full = calibrate + bridge in one call
// BYO provider key: pass your own Brave / Perplexity / OpenAI key
const result = await fetch("https://api.temporalblock.com/api/v1/full", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": process.env.TBLK_API_KEY,
  },
  body: JSON.stringify({
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    syncTier: "snippet",         // "snippet" | "briefing" | "deep"
    syncProvider: "perplexity",  // or "brave" | "serpapi" | "openai" | "anthropic" | "google"
    syncApiKey: process.env.PERPLEXITY_API_KEY,
  }),
}).then((r) => r.json());

// result.calibrateBlock  → prepend to system prompt
// result.bridgeBlock     → append to system prompt (or inject as a user turn)
// result.meta            → anchor confidence, Spiral coordinate, tier info
syncTierSpeedOutputMin tier
snippetfastestShort factual summaryStandard
briefingmediumCited paragraph with sourcesStandard
deepslowerParallel fan-out, merged resultStandard+
Calibration is free — no provider key needed. BYO model keys (OpenAI / Anthropic / Gemini) are available on all paid plans; BYO search-provider keys (Perplexity / Brave) are Standard+ and the cost of the search is yours directly. Calibrate and bridge calls are never cached — each call hits the certified anchor and the live web.

Managed current-events library — GET /v1/events

Query the stored temporal-event library without coupling your integration to one upstream dataset. The library can grow across sources while preserving source identity and attribution in record metadata and operational logs.

Constraints

  • Pro or Enterprise plan
  • API-key ownership is enforced
  • Cursor-based pagination
  • UTC and Spiral-window filters

Library records

  • Canonical event time
  • Spiral coordinate
  • Source and retrieval metadata
  • Stable pagination cursor
curl "$BASE_URL/api/v1/events?limit=25" \
  -H "X-API-Key: $TEMPORALBLOCK_API_KEY"

What is meta.spiralCoordinate?

Every /v1/full and /v1/bridge response includes a spiralCoordinate in the meta field. It is a compact representation of where you are in time across 12 overlapping cycles — millisecond through millennium — each expressed as a phase in [0, 1). At the novice level you can ignore it. Advanced users use it to trigger skills based on where the Spiral is, build circular time-aware UIs, or pass it as a Spiral anchor to other calls. Spiral section →

Free bridge + Spiral preview on /v1/calibrate

Want to see a bridge briefing before wiring up a provider key? Add includeBridgePreview: true and a bridgePreviewQuery to any /v1/calibrate call. The response includes meta.bridgePreview — a short cited briefing run on our key at no cost. Rate-limited; omitted (never a 4xx) when the quota is hit. Every calibrate response also carries a free Spiral preview in meta.spiral and meta.spiralCoordinate on all tiers with no extra flag. For production traffic use /v1/bridge or /v1/full with your own syncApiKey.

// Free bridge + Spiral preview on /v1/calibrate (all tiers, no extra charge)
const result = await fetch("https://api.temporalblock.com/api/v1/calibrate", {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-API-Key": process.env.TBLK_API_KEY },
  body: JSON.stringify({
    model: "gpt-4o",
    includeBridgePreview: true,
    bridgePreviewQuery: "What is the current state of quantum computing hardware?",
  }),
}).then((r) => r.json());

// result.meta.bridgePreview → { query, briefing, citations, evidence }
//   omitted (not a 4xx) when rate-limited or operator key unavailable
// result.meta.spiral         → free basic Spiral preview, all tiers
// result.meta.spiralCoordinate → { scales, sin, cos } free on all tiers

Step 3 · Intermediate · LLM agent integration

Tools manifest for LLM agents

GET /api/v1/tools returns an OpenAI or Anthropic function-calling manifest of every API operation your key is entitled to invoke. Pass it directly to your LLM — the model then calls the right endpoints itself.

⚠ Fetch the manifest at bot startup, not at build time. Descriptions, schemas, and available tools change with releases. A stale manifest from before June 2026 is missing the timer-friendly simple trigger documentation entirely.
// Anthropic format — use ?format=anthropic so Claude sees short names
// ("calibrate", "create_skill") instead of the long internal identifiers
const { tools, baseUrl } = await fetch(
  "https://api.temporalblock.com/api/v1/tools?format=anthropic",
  { headers: { "X-API-Key": process.env.TBLK_API_KEY } }
).then((r) => r.json());

// tools[n] → { name, description, input_schema, displayName }
// Pass directly to anthropic.messages.create({ tools, … })

import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();

const msg = await anthropic.messages.create({
  model: "claude-opus-4-5",
  max_tokens: 1024,
  tools,
  messages: [{ role: "user", content: "What time is it for me?" }],
});
// OpenAI format (default) — tools[n].function.name is the stable dispatch key
const { tools, baseUrl } = await fetch(
  "https://api.temporalblock.com/api/v1/tools",
  { headers: { "X-API-Key": process.env.TBLK_API_KEY } }
).then((r) => r.json());

// Build a dispatch map once — key is function.name
const byName = Object.fromEntries(tools.map((t) => [t.function.name, t]));

// Execute one tool_call returned by the LLM
async function executeTool(name: string, args: Record<string, unknown>) {
  const t = byName[name];
  let path = t.path;
  const body: Record<string, unknown> = {};
  for (const [k, v] of Object.entries(args)) {
    if (path.includes(`{${k}}`)) {
      path = path.replace(`{${k}}`, String(v));
    } else {
      body[k] = v;
    }
  }
  const isRead = t.method === "GET" || t.method === "DELETE";
  return fetch(`${baseUrl}${path}`, {
    method: t.method,
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": process.env.TBLK_API_KEY!,
    },
    body: isRead ? undefined : JSON.stringify(body),
  }).then((r) => r.json());
}

Tool name fields:

  • function.name — stable long-form (e.g. calibration__post_v1_calibrate). Use as the OpenAI dispatch map key.
  • function.semanticName — terse snake_case (e.g. calibrate). Returned as name in ?format=anthropic.
  • function.displayName — human label (e.g. "Set Timer / Create Skill").

Step 4 · Intermediate · Standard+

Timers, alarms & schedules (Skills)

POST /api/v1/skills creates a skill — a row with a trigger and a webhook URL. When the trigger fires, the API POSTs a signed payload to your endpoint. Use this for timers, alarms, reminders, and recurring schedules.

Use kind: "simple" for timers. The simple trigger is the recommended interface for bots and automated agents. Supply everyMs for a countdown or recurring timer, or atUtcMs for a one-shot alarm at a specific UTC instant. No Spiral knowledge needed.
// 2-minute countdown timer
const { skill, webhookSecret } = await fetch(
  "https://api.temporalblock.com/api/v1/skills",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": process.env.TBLK_API_KEY,
    },
    body: JSON.stringify({
      label: "2 minute timer",
      trigger: { kind: "simple", everyMs: 120_000 },  // 2 min = 120,000 ms
      webhookUrl: "https://your-app.example.com/webhook",
    }),
  }
).then((r) => r.json());

// ⚠ webhookSecret is shown EXACTLY ONCE — save it now.
// It is the signing secret for verifying incoming webhook payloads.
// Alarm at a specific UTC instant
trigger: { kind: "simple", atUtcMs: Date.now() + 30 * 60_000 }  // 30 min from now

// Recurring — every hour, forever
trigger: { kind: "simple", everyMs: 3_600_000, repeat: true }

// Recurring with a cap — fire 12 times then stop
trigger: { kind: "simple", everyMs: 3_600_000, repeat: true, maxFires: 12 }

// Common everyMs values:
// 1 minute  =  60_000
// 5 minutes = 300_000
// 2 hours   = 7_200_000
// 1 day     = 86_400_000
// Verify an incoming webhook — same derivation for both signature headers
import { createHash, createHmac } from "node:crypto";

function verifyWebhook(req: Request, rawSecret: string): boolean {
  const sigHeader = req.headers.get("X-Tblk-Signature") ?? "";
  const [scheme, incoming] = sigHeader.split("=", 2);
  if (scheme !== "sha256" || !incoming) return false;

  const body = await req.text();
  const signingKey = createHash("sha256").update(rawSecret).digest("hex");
  const expected = createHmac("sha256", signingKey).update(body).digest("hex");
  return expected === incoming;
  // Also check X-Tblk-Account-Signature using your stable account key
}
trigger.kindRequired fieldsUse caseTier
simpleeveryMs or atUtcMsTimers, alarms, recurring — recommended for botsStandard
atatUtcMsOne-shot at exact UTC epoch msStandard
afterrung, fractionOfCycle, anchorUtcMsSpiral-rung offset from an anchorStandard
recurrung, phaseFire each time the Spiral hits a phase on a rungStandard
displaceFromanchorUtcMs, op, thresholdMsFire when Spiral displacement exceeds thresholdPro
whenStateMatchesstateKey, conditionFire when a State KV value satisfies a predicateStandard
whenPaceBelowthresholdTokensPerSec, graceAfterProgress?Fire when output pacing drops below thresholdStandard
whenPhaseRecursstreamKeyFire on novel recurrence in an event streamStandard

Retry schedule on non-2xx: 1 min → 5 min → 30 min → 2 h → 12 h (5 total). Return 2xx immediately — do not block on downstream processing.

Manual arm — a "press here to start" button

Creating a skill and starting its countdown are usually the same instant. Manual arm splits them: create the skill ahead of time, unarmed, then start the clock the moment a real person clicks a button in your own UI — not when your server happened to call the API. Reusable pattern: create a skill, render a chip Unarmed with a button, and flip it to Armed on click.

Only valid with trigger.kind: "after". Set armPolicy: "manual" at create time. The skill comes back status: "unarmed" with a one-shot armToken — that token, not your X-API-Key, is what authorizes the arm call, so it's safe to send to the browser and wire straight to a button. POST /v1/skills/{id}/arm needs no API key at all. If nobody presses the button before armExpiresAtMs (default 15 min), the skill auto-cancels.

1. Server-side: create the skill unarmed

// Your backend — needs your real API key. Do this ahead of the button render,
// e.g. as soon as the chat turn that offers "press here to start" is generated.
const { skill, webhookSecret, armToken, armExpiresAtMs } = await fetch(
  "https://api.temporalblock.com/api/v1/skills",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": process.env.TBLK_API_KEY,
    },
    body: JSON.stringify({
      label: "10 minute focus timer",
      trigger: { kind: "after", rung: "min", fractionOfCycle: 10 / 60 },
      webhookUrl: "https://your-app.example.com/webhook",
      armPolicy: "manual",
    }),
  }
).then((r) => r.json());

// skill.status === "unarmed" — the 10-minute clock has NOT started yet.
// Send skill.id + armToken down to the browser. Never send X-API-Key to the browser.

2. Browser: chip + button, wired to the arm endpoint

// Reusable vanilla-JS pattern — drop into any chat/UI framework.
// No API key here: armToken is the only credential this call needs.
async function armSkill(skillId, armToken, chipEl) {
  chipEl.textContent = "Arming…";
  const res = await fetch(
    `https://api.temporalblock.com/api/v1/skills/${skillId}/arm`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ armToken }),
    }
  );
  if (res.status === 200) {
    chipEl.textContent = "Armed — running";     // countdown starts NOW
  } else if (res.status === 410) {
    chipEl.textContent = "Expired — create a new one";
  } else if (res.status === 409) {
    chipEl.textContent = "Already armed";
  } else {
    chipEl.textContent = "Could not arm — try again";
  }
}

// <button onclick="armSkill(skillId, armToken, chip)">Start timer</button>
// <span id="chip">Unarmed</span>

The arm call is meant for a human's own click — never have an LLM or agent call it on its own. Discovery tools (GET /v1/tools) do not list it for that reason; only armPolicy on create is agent-visible.


Advanced · Patent Pending — U.S. Provisional App. No. 64/065,213

Spiral Block

The Temporal Spiral is a 12-rung coordinate system that maps any UTC instant to a phase in [0, 1) on every time scale from microseconds to millennia. GET /api/v1/spiral returns the live-anchored coordinate for the current moment.

// Live Spiral coordinate for right now
const coordinate = await fetch(
  "https://api.temporalblock.com/api/v1/spiral",
  { headers: { "X-API-Key": process.env.TBLK_API_KEY } }
).then((r) => r.json());

// coordinate.scales → { us, ms, s, min, hr, day, wk, mo, yr, dec, cen, mil }
// Each scale: { rung, phase, sin, cos, source, cycleStartUtcMs, cycleEndUtcMs }
// coordinate.spiralReadable → human string, e.g. "2026-hr-0.648"

// Pin to a specific past or future moment with atUtcMs:
const pinned = await fetch(
  "https://api.temporalblock.com/api/v1/spiral?atUtcMs=1780300000000",
  { headers: { "X-API-Key": process.env.TBLK_API_KEY } }
).then((r) => r.json());

Common pitfall — don't feed anchored-now back in as atUtcMs

To encode "now", omit atUtcMs entirely — the server uses its own certified anchored clock. Fetching /v1/spiral/anchored-now (or reading any client clock) and passing the value back as atUtcMs marks the request synthetic: the server can't vouch for a round-tripped timestamp, so trust collapses to the 7 calendar-derived scales and sub-day rungs lose their anchor guarantee. Reserve atUtcMs for genuinely historical or synthetic instants. At runtime, a synthetic response carries meta.trustedScales (the scales still vouched for) and meta.trustNote explaining how to regain sub-day trust.

RungScaleCycle lengthSource
usmicrosecond1 µslib-grounded
msmillisecond1 mslib-grounded
ssecond1 sanchor
minminute60 sanchor
hrhour3,600 sanchor
dayday86,400 sanchor
wkweek604,800 sanchor
momonthcalendar monthanchor
yryear365.25 daysanchor
decdecade10 yearsanchor
cencentury100 yearsanchor
milmillennium1,000 yearsanchor

Sub-second rungs (us, ms) are library-grounded — derived from system clock without NTP correction. All other rungs are anchored to the certified NTP/NTS ensemble. Full Spiral docs →

Embed a Spiral Block in /v1/full Standard+

Pass includeSpiralBlock: true on /v1/full to receive a system-prompt-ready spiralBlock string alongside calibration and bridge output. Standard returns the scalar confidence; Pro adds the directional breakdown and sync-cycle phase. Lite keys receive a meta.spiral.upgradeReason hint instead and the block is omitted.

body: JSON.stringify({
  includeSpiralBlock: true,
  // ... other /v1/full fields
})

// result.spiralBlock                         → system-prompt-ready phase string (Standard+)
// result.meta.spiral.confidenceMs            → scalar confidence in ms (Standard+)
// result.meta.spiral.confidenceBreakdown     → { fromLastSyncMs, sampleDispersionMs, toNextSyncMs } (Pro+)
// result.meta.spiral.syncCyclePhase          → { msSinceLastSync, msUntilNextSync, fractionThroughCycle } (Pro+)
// result.meta.spiral.upgradeReason           → plain-language upsell hint (Lite only)

Advanced · Standard+ for writes

State KV

A simple key-value store scoped to your API key. Store any JSON value under a string key. Use it as agent memory, or as the trigger condition for a whenStateMatches skill.

const BASE = "https://api.temporalblock.com/api/v1/state";
const headers = { "Content-Type": "application/json", "X-API-Key": process.env.TBLK_API_KEY };

// Write a value (Standard+)
await fetch(`${BASE}/session-mode`, {
  method: "PUT",
  headers,
  body: JSON.stringify({ value: "focus" }),
});

// Read a value (all tiers)
const { value } = await fetch(`${BASE}/session-mode`, { headers }).then((r) => r.json());

// Delete
await fetch(`${BASE}/session-mode`, { method: "DELETE", headers });

// List all keys
const { keys } = await fetch(`${BASE}?prefix=session`, { headers }).then((r) => r.json());

// Trigger a skill when the value becomes "urgent":
trigger: {
  kind: "whenStateMatches",
  stateKey: "session-mode",
  condition: { eq: "urgent" },
}

Advanced · Distributed systems

Causal Block

Strict event ordering across every node — useful for distributed systems that need a single authoritative "happened-before" relationship regardless of network clock drift. Three endpoints: /now, /merge, and /compare.

const BASE = "https://api.temporalblock.com/api/v1/causal";
const H = { "Content-Type": "application/json", "X-API-Key": process.env.TBLK_API_KEY };

// Stamp the current moment — returns a causal clock vector
const { clock } = await fetch(`${BASE}/now`, { method: "POST", headers: H }).then(r => r.json());

// Merge two clocks from different nodes (returns the union — the "latest" happened-before)
const merged = await fetch(`${BASE}/merge`, {
  method: "POST",
  headers: H,
  body: JSON.stringify({ clocks: [clockA, clockB] }),
}).then(r => r.json());

// Compare two clocks — returns "before" | "after" | "concurrent"
// (Pro+ only)
const { relation } = await fetch(`${BASE}/compare`, {
  method: "POST",
  headers: H,
  body: JSON.stringify({ a: clockA, b: clockB }),
}).then(r => r.json());

/now and /merge are trickle-limited on Lite. /compare requires Pro+. Full Causal Block docs →


Advanced · Standard+ for live sessions

Output Pacing

Track your model's token output rate against an authoritative Spiral-anchored clock. The API supplies elapsed time, rate, and estimated finish — it does not count tokens itself. Use it to detect when a model is running slow, stalled, or ahead of a budget.

const BASE = "https://api.temporalblock.com/api/v1/pacing";
const H = { "Content-Type": "application/json", "X-API-Key": process.env.TBLK_API_KEY };

// 1. Open a session at the start of the LLM response stream
const { session } = await fetch(`${BASE}/sessions`, {
  method: "POST",
  headers: H,
  body: JSON.stringify({ model: "claude-opus-4-5", targetTokensPerSec: 40 }),
}).then(r => r.json());
const sessionId = session.id;

// 2. Send tick events as tokens arrive — include your own token count
await fetch(`${BASE}/sessions/${sessionId}/ticks`, {
  method: "POST",
  headers: H,
  body: JSON.stringify({ tokensSinceLastTick: 32 }),
}).then(r => r.json());
// Response: { elapsedMs, tokensPerSec, estimatedFinishMs, spiralCoordinate, … }

// 3. Close the session when the stream ends
await fetch(`${BASE}/sessions/${sessionId}/close`, { method: "POST", headers: H });

// Stateless estimate — no session required (all tiers)
const estimate = await fetch(`${BASE}/estimate?tokens=500&model=claude-opus-4-5`, {
  headers: H,
}).then(r => r.json());

Live sessions require Standard+. The stateless /estimate endpoint is available on all tiers. Full Output Pacing docs →


Learned from real integrations

Common pitfalls

① LLM reports the wrong local time

Cause: timezone was omitted from the calibrate call. The block contains UTC and the LLM reports it as the user's local time — which can be 5–12 hours off.

// ✗ Wrong — returns UTC, LLM will show the wrong hour
body: JSON.stringify({})

// ✓ Correct — always detect and forward the user's timezone
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
body: JSON.stringify({ timezone })

// If timezone is unknowable, pass "UTC" explicitly —
// the block labels it UTC so the LLM doesn't misrepresent it
body: JSON.stringify({ timezone: "UTC" })

② LLM says "I can't create a timer"

Cause: the tool manifest was fetched before June 2026, or the LLM's tool context doesn't include the skills tool at all. The create_skill tool (semantic name) creates timers — but only if the description in the manifest uses the words "timer" and "alarm". Refresh the manifest.

// Refresh at bot startup — not at build time
const { tools } = await fetch(
  "https://api.temporalblock.com/api/v1/tools?format=anthropic",
  { headers: { "X-API-Key": process.env.TBLK_API_KEY } }
).then((r) => r.json());
// The June 2026 manifest says "Set a timer, alarm, reminder…" in the description.
// A cached pre-June manifest may not.

③ Timer request returns 400 LEGACY_TRIGGER_REJECTED

Cause: durationMs, fireAtUtcMs, rrule, and similar fields are pre-v7 and are explicitly rejected. Use kind: "simple" with everyMs instead.

// ✗ Wrong — durationMs is a rejected legacy field
trigger: { kind: "after", durationMs: 120000 }

// ✓ Correct — simple trigger with everyMs
trigger: { kind: "simple", everyMs: 120000 }

④ Webhook secret not saved

Cause: the webhookSecret field in the skill creation response is shown exactly once and cannot be retrieved again. Save it immediately to your secrets store. If lost, delete the skill and recreate it.

⑤ Calibrate or bridge results cached

Cause: caching the calibrate response by timezone means every user in that zone gets a stale clock for the TTL duration — including the wrong minute and hour. Calibrate is cheap and must be called fresh per conversation. Bridge should likewise not be cached — the live search result is the point.


temporalBLOCK

Calibrate your A.I.’s temporal clock

temporalBLOCK and Temporal Spiral are trademarks of COREinsights.

No tracking cookies — site preferences stay in your browser.

© 2026 COREinsights. Patent pending.