ChronoShield API Docs

API Documentation

ChronoShield API is a REST API that validates, resolves, and converts datetimes across timezones with explicit handling of DST ambiguity. It catches the edge cases that cause silent production bugs — invalid times that don't exist (spring-forward gaps) and ambiguous times that occur twice (fall-back overlaps).

Base URL https://chronoshieldapi.com
v1.3.0 — versioned, stable API PostgreSQL-backed key & usage persistence 78 tests + ~30k zone×timestamp parity checks (adversarial DST edge cases across 9+ timezone regimes) Live status page

Not needed for simple UTC display formatting. Built for apps that interpret and act on user-entered local times.

When should I use ChronoShield API?

Use it whenever your application accepts, stores, or acts on local datetimes in any timezone. Specifically:

Scheduling systems — validate user-picked times before storing
Cron/workflow automation — ensure jobs don't skip or double-fire during DST
Billing & payments — process charges at the correct local time
AI agents — give your agent a tool to validate times before booking
Calendar integrations — convert between timezones with correct offsets
Booking platforms — prevent reservations at non-existent times

Real-World Scenarios

These are the exact situations where timezone bugs happen in production. Here's how ChronoShield API prevents each one.

Scenario 1: Scheduling a meeting during a DST gap

A user in New York picks March 8, 2026 at 2:30 AM for a standup. That time will never exist — clocks jump from 2:00 AM to 3:00 AM.

Without ChronoShield API

Your app stores 2:30 AM. The calendar event either fires at 3:30 AM (wrong) or not at all (worse). No error is raised.

With ChronoShield API

Call /validate → get status: "invalid" + suggested_fixes: ["3:00 AM"]. Show the user: "That time doesn't exist. Use 3:00 AM instead?"

Scenario 2: Cron job during a fall-back overlap

A billing job runs at 1:30 AM on November 1, 2026 in New York. Clocks fall back, so 1:30 AM happens twice — once at UTC-4 and again at UTC-5.

Without ChronoShield API

The job runs twice (charging customers double), or the system silently picks one offset and processes at the wrong moment.

With ChronoShield API

Call /resolve with ambiguous: "earlier" → get a single deterministic UTC instant. Job runs exactly once.

Scenario 3: AI agent booking a flight

An AI assistant is asked: "Book me a 2:30 AM departure from JFK on March 8." The agent needs to convert this to UTC for the airline API.

Without ChronoShield API

The agent converts naively, gets the wrong UTC time, and books a flight that departs at the wrong hour. The user misses their flight.

With ChronoShield API

Agent calls validate_local_datetime tool → sees DST_GAP → tells user "That time doesn't exist, the next available time is 3:00 AM. Should I use that?"

Quick Start

Get your first timezone-safe API call working in under 2 minutes:

  1. Get a free API key — enter your email on the home page (no credit card required)
  2. Copy the curl command below — replace YOUR_API_KEY with your key
  3. Run it — you'll get back a DST gap detection with suggested fixes
POST /v1/datetime/validate
curl -X POST https://chronoshieldapi.com/v1/datetime/validate \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "local_datetime": "2026-03-08T02:30:00",
    "time_zone": "America/New_York"
  }'
Response 200 — invalid (DST gap)
{
  "status": "invalid",
  "reason_code": "DST_GAP",
  "message": "This time does not exist due to DST transition.",
  "suggested_fixes": [
    { "strategy": "next_valid_time", "local_datetime": "2026-03-08T03:00:00" },
    { "strategy": "previous_valid_time", "local_datetime": "2026-03-08T01:59:59" }
  ]
}

Authentication

All /v1/* endpoints require an API key via the x-api-key header:

x-api-key: cg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Getting a key

  1. Visit the landing page and click "Get Free API Key"
  2. Enter your email — a key prefixed with cg_live_ is generated instantly
  3. Store it securely — it won't be shown again

Tiers

TierPriceRequests / month
Free$01,000
Pro$19/month100,000
Enterprise Starter$250/mo (annual)~417k/mo (5M/yr included)
Enterprise Growth$800/mo (annual)~833k/mo (10M/yr included)
Enterprise StrategicCustomCustom

Enterprise plans include invoice billing, SLA response times, and declining overage rates. Full pricing details →

Starter — best for one team needing annual billing & SLAs. Growth — best for multi-product or high-volume SaaS. Strategic — custom volume, dedicated environments, procurement & legal support.

How Enterprise onboarding works

After you sign the Order Form and your first invoice clears, your existing API key gets upgraded in place — no new key, no header swap, no code change. The same x-api-key you've already integrated continues to work, just with the negotiated higher monthly limit.

  1. Sign up at the homepage for a free-tier key (any email, no card).
  2. Email sales@chronoshieldapi.com with your expected volume, use case, and target go-live date.
  3. We send a one-page Order Form + MSA + DPA bundle for e-signature. Legal review usually takes 1–3 weeks.
  4. Once signed and the first invoice is paid (instant for cards, 3–5 business days for ACH), provisioning happens within 24 hours.
  5. You receive a confirmation email with your new monthly limit. Same key, same headers, higher ceiling.

Already have a Pro account? Same flow — we upgrade your existing key from Pro to Enterprise. No interruption, no migration.

Unauthorized response

HTTP 401
{ "error": "Unauthorized: invalid or missing API key" }

Which endpoint should I use?

Most integrations only need one or two of the four endpoints. This decision tree will get you to the right one in under 30 seconds.

Q. Do you already have a UTC instant?

"What's this in the user's local timezone?"

NO → You have a local datetime + IANA timezone. What do you need to do with it?
Check if it's safe to act on?
use /v1/datetime/validate

Returns DST_GAP, DST_OVERLAP, or INVALID_TIMEZONE. Lets your app decide.

Need a single deterministic UTC instant?
use /v1/datetime/resolve

You pre-pick the policy: earlier, later, reject, next_valid_time, etc.

Need to process many at once?
use /v1/datetime/batch

Up to 100 mixed validate/resolve/convert operations. Partial failures are isolated per item.

Building an AI agent integration? Start at /docs/ai-agents instead — the agent docs cover MCP setup, tool schemas, and recommended agent behavior per reason code.

POST

/v1/datetime/validate

Check whether a local datetime is valid, invalid (DST gap), or ambiguous (DST overlap) in the given timezone.

When to use: Before storing or acting on a user-provided local time. Call this first to detect problems, then decide how to handle them — or use /resolve to handle them automatically.

Request body

FieldTypeRequiredDescription
local_datetimestringYesISO 8601 local datetime without offset, e.g. 2026-03-08T02:30:00
time_zonestringYesIANA timezone identifier, e.g. America/New_York

Response — valid time

{
  "status": "valid",
  "message": "The provided datetime is valid in the given timezone."
}

Response — DST gap (invalid)

{
  "status": "invalid",
  "reason_code": "DST_GAP",
  "message": "This time does not exist due to DST transition.",
  "suggested_fixes": [
    { "strategy": "next_valid_time", "local_datetime": "2026-03-08T03:00:00" },
    { "strategy": "previous_valid_time", "local_datetime": "2026-03-08T01:59:59" }
  ]
}

Response — DST overlap (ambiguous)

{
  "status": "ambiguous",
  "reason_code": "DST_OVERLAP",
  "message": "This time is ambiguous due to DST transition (fall-back).",
  "possible_instants": [
    { "offset": "-04:00", "instant_utc": "2026-11-01T05:30:00.000Z" },
    { "offset": "-05:00", "instant_utc": "2026-11-01T06:30:00.000Z" }
  ]
}

Response — invalid timezone

{
  "status": "invalid",
  "reason_code": "INVALID_TIMEZONE",
  "message": "Invalid IANA timezone: Fake/Zone"
}
POST

/v1/datetime/resolve

Resolve a local datetime to a single UTC instant, automatically handling DST gaps and overlaps using your specified policy.

When to use: When you need a definitive UTC timestamp from a local time and want deterministic behavior for edge cases.

Request body

FieldTypeRequiredDescription
local_datetimestringYesISO 8601 local datetime
time_zonestringYesIANA timezone identifier
resolution_policyobjectNoEdge-case handling (see below)

Resolution policies

resolution_policy.ambiguous

When the time occurs twice (DST overlap):

  • "earlier" (default) — first occurrence
  • "later" — second occurrence
  • "reject" — return 400 error

resolution_policy.invalid

When the time doesn't exist (DST gap):

  • "next_valid_time" (default) — jump forward
  • "previous_valid_time" — jump backward
  • "reject" — return 400 error

Example request

{
  "local_datetime": "2026-11-01T01:30:00",
  "time_zone": "America/New_York",
  "resolution_policy": {
    "ambiguous": "earlier",
    "invalid": "next_valid_time"
  }
}

Success response

{
  "instant_utc": "2026-11-01T05:30:00.000Z",
  "offset": "-04:00"
}
FieldTypeDescription
instant_utcstringResolved UTC instant (ISO 8601, Z suffix)
offsetstringUTC offset that was applied (e.g. -04:00)

Rejected response (policy = reject)

HTTP 400
{
  "error": "This time is ambiguous due to DST transition and policy is set to reject.",
  "code": "DST_OVERLAP"
}
POST

/v1/datetime/convert

Convert a UTC instant to a local datetime in the target timezone. This is always unambiguous — one UTC instant maps to exactly one local time.

When to use: Displaying stored UTC timestamps to users in their local timezone.

Request body

FieldTypeRequiredDescription
instant_utcstringYesISO 8601 UTC datetime (must end with Z)
target_time_zonestringYesIANA timezone identifier

Example

Request

{
  "instant_utc": "2026-06-15T15:00:00Z",
  "target_time_zone": "Europe/London"
}

Response

{
  "local_datetime": "2026-06-15T16:00:00",
  "offset": "+01:00",
  "time_zone": "Europe/London"
}
POST

/v1/datetime/batch

Process up to 100 validate, resolve, and convert operations in a single request.

When to use: Importing calendar events, migrating scheduling data, or any scenario where you need to check multiple datetimes at once. Partial failures are handled gracefully — each item succeeds or fails independently.

Request
curl -X POST https://chronoshieldapi.com/v1/datetime/batch \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "items": [
      { "operation": "validate", "local_datetime": "2026-03-08T02:30:00", "time_zone": "America/New_York" },
      { "operation": "resolve", "local_datetime": "2026-11-01T01:30:00", "time_zone": "America/New_York", "resolution_policy": { "ambiguous": "earlier" } },
      { "operation": "convert", "instant_utc": "2026-06-15T15:00:00Z", "target_time_zone": "Europe/London" }
    ]
  }'
Response 200 — 3 succeeded, 0 failed
{
  "results": [
    { "index": 0, "operation": "validate", "success": true, "data": { "status": "invalid", "reason_code": "DST_GAP", ... } },
    { "index": 1, "operation": "resolve", "success": true, "data": { "instant_utc": "2026-11-01T05:30:00.000Z", "offset": "-04:00" } },
    { "index": 2, "operation": "convert", "success": true, "data": { "local_datetime": "2026-06-15T16:00:00", "offset": "+01:00" } }
  ],
  "total": 3,
  "succeeded": 3,
  "failed": 0
}

Request fields

FieldTypeRequiredDescription
itemsarrayYesArray of 1–100 operation objects
items[].operationstringYes"validate", "resolve", or "convert"

Each item includes the same fields as its respective individual endpoint. Failed items return success: false with an error object.

GET

/health

Returns service health status. No authentication required.

{ "status": "ok" }

Enums & Constants

status

ValueMeaning
validThe datetime exists exactly once in the given timezone
invalidThe datetime does not exist (DST gap — clocks skipped forward)
ambiguousThe datetime exists twice (DST overlap — clocks fell back)

reason_code

ValueMeaning
DST_GAPTime falls in a spring-forward gap
DST_OVERLAPTime falls in a fall-back overlap
INVALID_TIMEZONEThe timezone is not a valid IANA identifier

Error Handling

All errors return JSON with an error field and an appropriate HTTP status code.

StatusMeaningWhen it happens
400Bad RequestMalformed datetime, missing fields, invalid timezone, or rejected by resolution policy
401UnauthorizedMissing or invalid API key
429Rate LimitedMonthly request quota exceeded
500Internal ErrorUnexpected server error

Validation error example

HTTP 400
{
  "error": "Validation failed",
  "details": [
    {
      "code": "invalid_string",
      "message": "Must be ISO 8601 local datetime (e.g. 2026-03-08T02:30:00)",
      "path": ["local_datetime"]
    }
  ]
}

Rate Limits

TierMonthly limitEffective daily ratep95 latency
Free1,000~33/day< 50ms
Pro100,000~3,300/day< 50ms
Enterprise Starter~417k (5M/yr)~13,700/day< 50ms
Enterprise Growth~833k (10M/yr)~27,400/day< 50ms
Enterprise StrategicCustomCustom< 50ms

All endpoints use pure computation with cached timezone lookups — no external I/O in the request path.

Official SDKs

Maintained by the ChronoShield API team. Available now on npm (npm install chronoshield) and PyPI (pip install chronoshield). Source on GitHub.

TypeScript / JavaScript

import { ChronoShieldClient } from "chronoshield";

const client = new ChronoShieldClient({
  baseUrl: "https://chronoshieldapi.com",
  apiKey: "YOUR_API_KEY",
});

const result = await client.validate({
  local_datetime: "2026-03-08T02:30:00",
  time_zone: "America/New_York",
});

console.log(result.status); // "invalid"

Python

from chronoshield import ChronoShieldClient

client = ChronoShieldClient(
    base_url="https://chronoshieldapi.com",
    api_key="YOUR_API_KEY",
)

result = client.validate("2026-03-08T02:30:00", "America/New_York")
print(result.status)  # "invalid"

Production patterns

Knowing the endpoints isn't the same as knowing where to put the call in your app. Below are seven patterns that map ChronoShield onto common production architectures — pick the one that matches your code.

Pattern 1

Validate before storing user-selected local time

When: A user picks a date+time in your UI (booking form, scheduler, "remind me at...") and you're about to write it to the database.

Where in your app: Inside the request handler that receives the form submission, BEFORE the INSERT.

// Pseudocode — Express/Fastify/etc. handler
app.post("/bookings", async (req, res) => {
  const { local_datetime, time_zone } = req.body;

  const check = await chronoshield.validate({ local_datetime, time_zone });
  if (check.status !== "valid") {
    return res.status(400).json({
      error: "Invalid time",
      reason: check.reason_code,
      suggested_fixes: check.suggested_fixes,
    });
  }

  // Safe to store. Resolve to UTC and persist.
  const utc = await chronoshield.resolve({ local_datetime, time_zone });
  await db.bookings.create({ instant_utc: utc.instant_utc, local_datetime, time_zone });
});
Pattern 2

Resolve before scheduling a job

When: User says "run this every day at 9am Eastern." Your job runner needs a UTC timestamp to fire on.

Where in your app: When computing the next fire time for a recurring job. Recompute after each run — don't add 24h to the previous fire (DST drifts that).

// After each run, compute the next fire time fresh
async function nextFire(localTime, tz) {
  const tomorrow = addDays(localToday(tz), 1);
  const localDt = `${tomorrow}T${localTime}`;

  const resolved = await chronoshield.resolve({
    local_datetime: localDt,
    time_zone: tz,
    resolution_policy: { invalid: "next_valid_time", ambiguous: "earlier" },
  });
  return resolved.instant_utc;
}
Pattern 3

Store UTC instant + original local time + timezone

When: Always — this is the schema you should use for any time-bound record.

Why all three: UTC is for sorting/triggering; local + tz is for re-rendering and re-resolving if tzdb rules change after storage.

-- PostgreSQL example
CREATE TABLE bookings (
  id UUID PRIMARY KEY,
  instant_utc      TIMESTAMPTZ NOT NULL,   -- for indexing, sorting, scheduling
  local_datetime   TEXT NOT NULL,          -- original user input
  time_zone        TEXT NOT NULL,          -- IANA zone they picked in
  ...
);
CREATE INDEX ON bookings (instant_utc);

Storing only UTC means you lose the user's intent if a tzdb rule changes (e.g. a country abolishes DST). Keeping all three lets you re-resolve cleanly.

Pattern 4

Ask the user when validate returns DST_GAP

When: The local time the user picked doesn't exist (spring-forward).

UI pattern: Render suggested_fixes[0].local_datetime as a "did you mean?" prompt. Don't silently shift — the user picked that time for a reason; let them confirm or pick differently.

if (result.reason_code === "DST_GAP") {
  showModal({
    title: "That time doesn't exist on this day",
    body: `Clocks jump from 02:00 to 03:00 for daylight saving. Want to use ${result.suggested_fixes[0].local_datetime} instead?`,
    actions: [
      { label: "Use 3:00 AM", onClick: () => submit(result.suggested_fixes[0].local_datetime) },
      { label: "Pick another time", onClick: () => reopenPicker() },
    ],
  });
}
Pattern 5

Apply explicit policy on DST_OVERLAP

When: The local time happens twice (fall-back). Two valid UTC instants exist; you have to pick one.

Recommended defaults by use case:

  • Calendar / scheduling: earlier — the user means "the first time 1:30 AM happens that day"
  • Billing / cron: earlier + idempotent job design — cheaper than charging twice
  • High-stakes / financial: reject — force the caller to ask the user
const resolved = await chronoshield.resolve({
  local_datetime, time_zone,
  resolution_policy: {
    ambiguous: "earlier",        // or "later" / "reject"
    invalid: "next_valid_time",  // or "previous_valid_time" / "reject"
  },
});
Pattern 6

Batch-validate generated schedules

When: Importing a calendar (.ics), processing a CSV of recurring events, or an LLM has generated a list of times to schedule.

Why batch: One round-trip vs. N. The batch endpoint isolates per-item failures so a single invalid time doesn't fail the whole import.

const result = await chronoshield.batch({
  items: scheduleItems.map(item => ({
    operation: "validate",
    local_datetime: item.start,
    time_zone: item.tz,
  })),
});

const invalid = result.results.filter(r => !r.success || r.data.status !== "valid");
if (invalid.length) {
  presentImportReview(invalid);  // let user fix bad rows before commit
}
Pattern 7

Use ChronoShield as an agent preflight tool

When: Building an LLM agent that books, schedules, bills, or reminds based on natural-language time references.

How: Install the chronoshield-mcp server (one config block) or wire validate_local_datetime as an OpenAI/Claude tool. The agent calls validate FIRST, asks the user to disambiguate on DST_GAP / DST_OVERLAP, then proceeds. Full guide: /docs/ai-agents.

AI Agent / Tool Integration

Full AI agent guide moved to its own page

ChronoShield is a preflight check for time-based AI agent actions. The dedicated guide covers MCP server setup (Claude Desktop, Cursor, Windsurf), OpenAI tool calling, Claude tool use, LangChain, Vercel AI SDK, recommended agent behavior per reason code, and end-to-end examples.

Open the AI agent guide

Need help?

General inquiries

info@chronoshieldapi.com

Technical support

support@chronoshieldapi.com

Enterprise & sales

sales@chronoshieldapi.com

Security concerns

security@chronoshieldapi.com