Developer API — Convert UTC to any time zone

CORS-open public endpoint. The no-code embed widget is free and keyless; raw JSON integrations use a free API key (1,000 requests/day). Built for embedding TimeZoneMeet conversions in your own app.

This is the live widget. ↙

Powered by the same /api/convert endpoint documented below. Paste two lines to put it on your own dashboard — no key, no build step.

Get the embed code

On this page

Endpoint

GEThttps://timezonemeet.app/api/convert

Converts a UTC instant into one or more IANA time zones. Returns the local wall-clock time, the same instant as a zoned ISO-8601 string, the UTC offset, the day of the week, and a DST flag for each zone.

Authentication

Two ways to use the API:

Pass your key any one of these ways:

# Header (recommended)
curl 'https://timezonemeet.app/api/convert?utc=now&tz=Europe/Paris' \
  -H 'X-API-Key: tzm_live_xxxxxxxx'

# Authorization: Bearer
curl 'https://timezonemeet.app/api/convert?utc=now&tz=Europe/Paris' \
  -H 'Authorization: Bearer tzm_live_xxxxxxxx'

# Query parameter (handy for quick tests; avoid in logs)
curl 'https://timezonemeet.app/api/convert?utc=now&tz=Europe/Paris&key=tzm_live_xxxxxxxx'

Keyless calls to /api/convert still work today (the widget uses that path) but are rate-limited and intended for the widget — production raw-data integrations should use a key. When you display the data, keep the attribution (see Terms).

Parameters

NameRequiredDescription
utc Yes ISO-8601 UTC timestamp, e.g. 2026-05-20T14:00:00Z (the trailing Z is required when using a literal timestamp). You may also pass the string now to convert the current instant.
tz Yes IANA time-zone identifier, e.g. Europe/Paris, America/Los_Angeles, Asia/Kolkata. Pass a comma-separated list (max 10) for multiple zones in a single request: Europe/Paris,Asia/Tokyo,UTC.

Response

JSON. Always returns { utc, results, provider, url, attribution } with one entry in results per zone you passed, in the order you passed them. The provider/url/attribution fields identify the data source — please surface the attribution where you display the data (see Terms).

{
  "utc": "2026-05-20T14:00:00.000Z",
  "provider": "TimeZoneMeet",
  "url": "https://timezonemeet.app",
  "attribution": "Times by TimeZoneMeet — https://timezonemeet.app",
  "results": [
    {
      "timezone": "Europe/Paris",
      "localTime": "2026-05-20 16:00",
      "iso": "2026-05-20T16:00:00+02:00",
      "offset": "+02:00",
      "offsetMinutes": 120,
      "abbreviation": "CEST",
      "dayOfWeek": "Wednesday",
      "isDst": true
    },
    {
      "timezone": "Asia/Kolkata",
      "localTime": "2026-05-20 19:30",
      "iso": "2026-05-20T19:30:00+05:30",
      "offset": "+05:30",
      "offsetMinutes": 330,
      "abbreviation": "GMT+5:30",
      "dayOfWeek": "Wednesday",
      "isDst": false
    }
  ]
}

Field reference

FieldTypeNotes
utcstringThe instant you requested, normalized to ISO-8601 with milliseconds. Echoes the resolved value when you passed now.
provider / urlstringThe data source (TimeZoneMeet) and its URL. Display the attribution where you show the data.
attributionstringReady-made attribution string: Times by TimeZoneMeet — https://timezonemeet.app.
results[].timezonestringThe IANA zone you asked for, echoed back.
results[].localTimestringWall-clock time formatted as YYYY-MM-DD HH:MM (24-hour). Truncated to the minute.
results[].isostringThe same instant rendered as a zoned ISO-8601 string, including seconds and the offset suffix (e.g. 2026-05-20T16:00:00+02:00).
results[].offsetstringOffset from UTC at this instant, formatted ±HH:MM. Correctly handles half-hour and 45-minute zones (India, Nepal, etc.).
results[].offsetMinutesnumberSame offset as a signed integer in minutes. Useful if you'd rather not parse the string.
results[].abbreviationstringThe zone's short name at this instant (e.g. EST, PDT). Best-effort: some Node/browser engines return GMT+5:30-style strings here instead of a regional abbreviation.
results[].dayOfWeekstringEnglish day name in the target zone (Monday, Tuesday, …).
results[].isDstbooleantrue if the zone is observing summer time at this instant. false for zones that don't observe DST.

Examples

curl

curl 'https://timezonemeet.app/api/convert?utc=2026-05-20T14:00:00Z&tz=Europe/Paris'

Multiple zones in one call:

curl 'https://timezonemeet.app/api/convert?utc=2026-05-20T14:00:00Z&tz=Europe/Paris,Asia/Tokyo,America/New_York'

Right now, in your local zone:

curl 'https://timezonemeet.app/api/convert?utc=now&tz=America/Los_Angeles'

JavaScript (browser or Node)

const url = new URL("https://timezonemeet.app/api/convert");
url.searchParams.set("utc", "2026-05-20T14:00:00Z");
url.searchParams.set("tz", "Europe/Paris,Asia/Tokyo");

const res = await fetch(url);
const data = await res.json();
for (const r of data.results) {
  console.log(`${r.timezone}: ${r.localTime} (${r.offset}, ${r.dayOfWeek})`);
}
// Europe/Paris: 2026-05-20 16:00 (+02:00, Wednesday)
// Asia/Tokyo:   2026-05-20 23:00 (+09:00, Wednesday)

Python

import requests
r = requests.get(
    "https://timezonemeet.app/api/convert",
    params={"utc": "2026-05-20T14:00:00Z", "tz": "Europe/Paris,Asia/Tokyo"},
    timeout=5,
)
r.raise_for_status()
for entry in r.json()["results"]:
    print(entry["timezone"], entry["localTime"], entry["offset"])

Embeddable widget — no code required

If you just want a live world-clock panel on your own dashboard without writing any fetch/render code, drop in the embeddable widget. It calls the /api/convert endpoint above for you, renders one row per zone, and ticks every minute. No build step, no dependencies, no API key.

Quick start

Paste a container plus the script anywhere on your page:

<div data-tzm-clock
     data-zones="America/New_York,Europe/London,Asia/Tokyo"></div>
<script src="https://timezonemeet.app/embed.js" async></script>

That renders:

New York   09:00  Mon
London     14:00  Mon
Tokyo      23:00  Mon

Add as many data-tzm-clock containers as you like — a single embed.js tag drives all of them. Up to 10 zones per widget (same limit as the endpoint).

Options

Configure each widget with data-* attributes on its container:

AttributeDescription
data-zones Required. Comma-separated IANA zones, in display order. Max 10. e.g. America/Los_Angeles,Europe/Paris,Asia/Kolkata.
data-show Comma-separated extras, off by default: offset appends the ±HH:MM UTC offset, dst shows a DST badge when a zone is on summer time, and utc prepends a UTC reference clock to the panel. Combine them: data-show="offset,dst,utc".
data-labels Optional comma-separated labels overriding the auto-derived city names (must match data-zones order). e.g. data-labels="HQ (SF),Berlin Office,Bengaluru Team".

Every widget shows a "Times by TimeZoneMeet" link back to the source. Attribution is required (see Terms), so there is no option to disable it.

Theming

The widget renders into your page (not an iframe), so it inherits your fonts and sits inside your layout. Every color and spacing value is a CSS custom property with a sensible default — scope overrides to the widget to match your dashboard:

<div data-tzm-clock
     data-zones="America/New_York,Europe/London,Asia/Singapore"
     data-show="offset,dst"
     style="--tzm-clock-bg:#111827; --tzm-clock-fg:#f9fafb;
            --tzm-clock-muted:#9ca3af; --tzm-clock-border:#1f2937;
            --tzm-clock-accent:#8b5cf6;"></div>

Available properties: --tzm-clock-bg, --tzm-clock-fg, --tzm-clock-muted, --tzm-clock-accent, --tzm-clock-border, --tzm-clock-radius, --tzm-clock-font.

Notes

Errors

Validation errors return HTTP 400; authentication/quota errors return 401/429. The body is always JSON { "error": "…" }.

StatusConditionBody
401Unknown/invalid key{"error":"invalid_api_key"}
401Revoked key{"error":"revoked_api_key"}
429Over daily quota{"error":"daily_quota_exceeded","limit":1000}
429Burst limit{"error":"rate_limited","retry_after":60}
400utc missingutc is required (ISO 8601 UTC timestamp like 2026-05-20T14:00:00Z, or 'now').
400utc unparseableutc could not be parsed. Use ISO 8601 (e.g. 2026-05-20T14:00:00Z) or 'now'.
400tz missingtz is required (IANA zone, e.g. Europe/Paris). Multiple zones may be comma-separated.
400Unknown zone in tzUnknown IANA timezone: <zone you passed>
400More than 10 zonesMaximum 10 zones per request.

Caching

Responses are explicitly cacheable. For a fixed UTC instant the same zone always returns the same result, so the server sets:

Cache-Control: public, max-age=3600, s-maxage=2592000

That's 1 hour in the browser, 30 days in any shared CDN. If you pass utc=now, the response is marked no-store instead.

If you're building a high-traffic integration, prefer fixed timestamps and let your CDN absorb the bulk of the load. Asking for "now" 10 times a second per user is wasteful — compute "now" client-side, round to the minute or hour you actually need, then call with the fixed timestamp.

Rate limits

Because keyless /api/convert responses are cacheable, the widget stays well under the limit by reusing CDN-cached responses. For keyed integrations, prefer fixed timestamps and cache on your own side.

Need more than 1,000/day? Drop us a note at contact with your use case — we're happy to raise the limit for legitimate integrations.

Tools & OpenAPI

The API ships a machine-readable OpenAPI 3.1 description:

GEThttps://timezonemeet.app/openapi.json

A note on zone names

The tz parameter accepts any identifier that's valid in the IANA Time Zone Database. Always prefer the canonical region/city form:

Three-letter abbreviations like EST, PST, and IST are ambiguous (IST means both Indian and Irish Standard Time, for example) and many of them don't observe DST when callers expect them to. The TimeZoneMeet blog has a longer post on why this matters.

Terms of use

← Return to TimeZoneMeet · Blog · Contact