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).
https://chronoshieldapi.com
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:
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.
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.
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.
The job runs twice (charging customers double), or the system silently picks one offset and processes at the wrong moment.
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.
The agent converts naively, gets the wrong UTC time, and books a flight that departs at the wrong hour. The user misses their flight.
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:
- Get a free API key — enter your email on the home page (no credit card required)
- Copy the curl command below — replace
YOUR_API_KEYwith your key - Run it — you'll get back a DST gap detection with suggested fixes
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"
}'
{
"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
- Visit the landing page and click "Get Free API Key"
- Enter your email — a key prefixed with
cg_live_is generated instantly - Store it securely — it won't be shown again
Tiers
| Tier | Price | Requests / month |
|---|---|---|
| Free | $0 | 1,000 |
| Pro | $19/month | 100,000 |
| Enterprise Starter | $250/mo (annual) | ~417k/mo (5M/yr included) |
| Enterprise Growth | $800/mo (annual) | ~833k/mo (10M/yr included) |
| Enterprise Strategic | Custom | Custom |
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.
- Sign up at the homepage for a free-tier key (any email, no card).
- Email sales@chronoshieldapi.com with your expected volume, use case, and target go-live date.
- We send a one-page Order Form + MSA + DPA bundle for e-signature. Legal review usually takes 1–3 weeks.
- Once signed and the first invoice is paid (instant for cards, 3–5 business days for ACH), provisioning happens within 24 hours.
- 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.
/v1/datetime/convert
"What's this in the user's local timezone?"
/v1/datetime/validate
Returns DST_GAP, DST_OVERLAP, or INVALID_TIMEZONE. Lets your app decide.
/v1/datetime/resolve
You pre-pick the policy: earlier, later, reject, next_valid_time, etc.
/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.
/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
| Field | Type | Required | Description |
|---|---|---|---|
| local_datetime | string | Yes | ISO 8601 local datetime without offset, e.g. 2026-03-08T02:30:00 |
| time_zone | string | Yes | IANA 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"
}
/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
| Field | Type | Required | Description |
|---|---|---|---|
| local_datetime | string | Yes | ISO 8601 local datetime |
| time_zone | string | Yes | IANA timezone identifier |
| resolution_policy | object | No | Edge-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"
}
| Field | Type | Description |
|---|---|---|
| instant_utc | string | Resolved UTC instant (ISO 8601, Z suffix) |
| offset | string | UTC 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"
}
/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
| Field | Type | Required | Description |
|---|---|---|---|
| instant_utc | string | Yes | ISO 8601 UTC datetime (must end with Z) |
| target_time_zone | string | Yes | IANA 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"
}
/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.
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" }
]
}'
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
| items | array | Yes | Array of 1–100 operation objects |
| items[].operation | string | Yes | "validate", "resolve", or "convert" |
Each item includes the same fields as its respective individual endpoint. Failed items return success: false with an error object.
/health
Returns service health status. No authentication required.
{ "status": "ok" }
Enums & Constants
status
| Value | Meaning |
|---|---|
| valid | The datetime exists exactly once in the given timezone |
| invalid | The datetime does not exist (DST gap — clocks skipped forward) |
| ambiguous | The datetime exists twice (DST overlap — clocks fell back) |
reason_code
| Value | Meaning |
|---|---|
| DST_GAP | Time falls in a spring-forward gap |
| DST_OVERLAP | Time falls in a fall-back overlap |
| INVALID_TIMEZONE | The timezone is not a valid IANA identifier |
Error Handling
All errors return JSON with an error field and an appropriate HTTP status code.
| Status | Meaning | When it happens |
|---|---|---|
| 400 | Bad Request | Malformed datetime, missing fields, invalid timezone, or rejected by resolution policy |
| 401 | Unauthorized | Missing or invalid API key |
| 429 | Rate Limited | Monthly request quota exceeded |
| 500 | Internal Error | Unexpected 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
| Tier | Monthly limit | Effective daily rate | p95 latency |
|---|---|---|---|
| Free | 1,000 | ~33/day | < 50ms |
| Pro | 100,000 | ~3,300/day | < 50ms |
| Enterprise Starter | ~417k (5M/yr) | ~13,700/day | < 50ms |
| Enterprise Growth | ~833k (10M/yr) | ~27,400/day | < 50ms |
| Enterprise Strategic | Custom | Custom | < 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.
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 });
});
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;
}
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.
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() },
],
});
}
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"
},
});
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
}
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 guideNeed help?
General inquiries
info@chronoshieldapi.comTechnical support
support@chronoshieldapi.comEnterprise & sales
sales@chronoshieldapi.comSecurity concerns
security@chronoshieldapi.com