True Solar Time API

Reference documentation for v1

Overview

The True Solar Time API converts a civil (clock) time and location into true solar time — the time the sundial reads. It applies the Equation of Time and the longitude correction from the timezone's standard meridian, then returns solar position, a calculation breakdown, and contextual prose explaining the history and physics behind the result.

Two input modes are supported: supply a free-text place_name (geocoded automatically) or supply latitude and longitude directly.

Base URL

https://your-host/api/v1

All endpoints are under /api/v1/. Responses are always application/json.

Authentication

Pass your key in the X-API-Key request header:

curl https://your-host/api/v1/solar-time \
  -H "X-API-Key: tst_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"date":"2026-03-30","local_time":"14:30","place_name":"Singapore"}'

Behaviour by configuration

EnforcementKey suppliedOutcome
OFF (default)NoneAllowed — rate-limited per client IP
OFF (default)Valid keyAllowed — rate-limited per key (higher limit)
OFF (default)Invalid keyAllowed — falls back to IP rate limiting
ON (API_KEY_REQUIRED=true)None401 MISSING_API_KEY
ON (API_KEY_REQUIRED=true)Invalid key401 INVALID_API_KEY
ON (API_KEY_REQUIRED=true)Valid keyAllowed — rate-limited per key

To request a key, see the key management page.

Rate Limiting

The API uses a fixed 60-second window. Limits vary by identity:

IdentityDefault limit
Anonymous (no key)60 rpm
API key — Free/Dev10 rpm
API key — Builder60 rpm
API key — Pro/Commercial120–300 rpm (set at issuance)

When the limit is reached you receive a 429 response. Wait for the number of seconds in the Retry-After header before retrying.

Rate limit headers

Every successful response (and every 429) carries these headers:

HeaderDescription
X-RateLimit-LimitYour request limit for the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets
Retry-AfterSeconds until reset (429 responses only)

429 response example

HTTP/1.1 429 Too Many Requests
Retry-After: 42
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1743350400

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Try again in 42 second(s).",
    "retry_after": 42
  }
}

Endpoints

POST /api/v1/solar-time

Canonical endpoint. Accepts a JSON body. Returns the full solar time result.

GET /api/v1/solar-time

Convenience alias for the POST endpoint. Accepts the same fields as query parameters. Useful for shareable links and browser testing. Returns the identical response schema.

Input modes

Exactly one mode must be used — they are mutually exclusive:

ModeRequired fieldsNotes
Place-name place_name Geocoded automatically. Returns 409 with candidates if the name is ambiguous across countries.
Coordinate latitude, longitude Skips geocoding. Recommended after resolving a 409. Coordinates are reverse-geocoded for display only.

Request fields

FieldTypeRequiredDescription
date string Yes ISO 8601 date. Example: 2026-03-30
local_time string Yes 24-hour local civil time. HH:MM or HH:MM:SS. Example: 14:30
place_name string Conditional Free-text place name. Mutually exclusive with latitude/longitude.
latitude float Conditional Decimal degrees, −90 to +90. Must be supplied with longitude.
longitude float Conditional Decimal degrees, −180 to +180. Must be supplied with latitude.

Response schema

A successful 200 response contains these top-level fields:

FieldTypeDescription
true_solar_timestringThe main result — sundial time at the given location (HH:MM:SS)
local_timestringCivil clock input echoed back as HH:MM:SS
utc_timestringUTC equivalent of the input time (HH:MM:SS)
utc_offsetstringUTC offset with DST applied (±HH:MM)
timezonestringIANA timezone identifier, e.g. Asia/Singapore
dstbooleanWhether Daylight Saving Time is in effect
datestringDate echoed from input (YYYY-MM-DD)
locationobjectplace_name, display_name, latitude, longitude, country_code
calculationobjectequation_of_time_minutes, longitude_correction_minutes, total_difference_minutes
solar_positionobjectazimuth (degrees, 0=N), elevation (degrees above horizon)
confidencestringHIGH / MEDIUM / LOW
historical_context_statusstringfound or not_found
summarystringHuman-readable result explanation (English)
historical_contextstringCivil-time history for this timezone (English)
solar_contextstringEquation of Time and longitude physics explanation (English)
validationstringSanity-check notes and physics bullet points
shareobjecttext, url_params, url — for building shareable links

Full example response

{
  "true_solar_time": "13:51:06",
  "local_time": "14:30:00",
  "utc_time": "06:30:00",
  "utc_offset": "+08:00",
  "timezone": "Asia/Singapore",
  "dst": false,
  "date": "2026-03-30",
  "location": {
    "place_name": "Singapore",
    "display_name": "Singapore, Central Region, Singapore",
    "latitude": 1.3521,
    "longitude": 103.8198,
    "country_code": "SG"
  },
  "calculation": {
    "equation_of_time_minutes": -4.1,
    "longitude_correction_minutes": -64.8,
    "total_difference_minutes": -68.9
  },
  "solar_position": { "azimuth": 271.34, "elevation": 28.17 },
  "confidence": "HIGH",
  "historical_context_status": "found",
  "summary": "In Singapore, the Sun is running 1 hour 8 minutes 54 seconds behind the clock.",
  "historical_context": "Singapore adopted UTC+8 permanently in 1982...",
  "solar_context": "The Equation of Time is -4.1 min on this date...",
  "validation": "  \u2713 EoT within expected range\n  \u2713 Longitude correction within expected range",
  "share": {
    "text": "True Solar Time in Singapore on 30 Mar 2026 at 14:30 \u2192 13:51",
    "url_params": "date=2026-03-30&local_time=14:30&latitude=1.3521&longitude=103.8198",
    "url": "/?date=2026-03-30&local_time=14:30&latitude=1.3521&longitude=103.8198"
  }
}

Errors

All errors follow this envelope:

{ "error": { "code": "MACHINE_READABLE_CODE", "message": "Human-readable description." } }

Error codes

CodeStatusMeaning
MISSING_API_KEY401No key supplied and enforcement is on
INVALID_API_KEY401Key not recognised or deactivated
PLACE_NOT_FOUND404Geocoder returned no results for place_name
AMBIGUOUS_LOCATION409Place name matched locations in multiple countries — candidates array included
INVALID_REQUEST400/422Bad date/time format, out-of-range coordinates, or conflicting input modes
TIMEZONE_RESOLUTION_FAILED422Coordinates resolve to international waters with no IANA timezone
RATE_LIMIT_EXCEEDED429Too many requests — see Retry-After header
CALCULATION_FAILED500Internal error during solar calculation

Handling a 409 — ambiguous location

When a place name matches locations in more than one country, the response includes a candidates array. Resolve by resubmitting in coordinate mode:

# 1. First request returns 409:
POST /api/v1/solar-time
{ "date": "2026-03-30", "local_time": "14:30", "place_name": "Springfield" }

# Response:
{ "error": { "code": "AMBIGUOUS_LOCATION", "message": "...", "candidates": [
    { "display_name": "Springfield, Illinois, US", "latitude": 39.7817, "longitude": -89.6501, "recommended": true },
    ...
  ]
}}

# 2. Resubmit with chosen candidate's coordinates:
POST /api/v1/solar-time
{ "date": "2026-03-30", "local_time": "14:30", "latitude": 39.7817, "longitude": -89.6501 }

Status codes

CodeMeaning
200Calculation succeeded
400Invalid query parameters (GET alias only)
401Missing or invalid API key (enforcement mode only)
404Place not found
409Ambiguous place name — candidates provided
422Validation failure or timezone resolution error
429Rate limit exceeded
500Internal calculation error

Examples

Place-name mode (curl)

curl -s -X POST https://your-host/api/v1/solar-time \
  -H "Content-Type: application/json" \
  -H "X-API-Key: tst_your_key_here" \
  -d '{
    "date": "2026-03-30",
    "local_time": "14:30",
    "place_name": "Singapore"
  }'

Coordinate mode (curl)

curl -s -X POST https://your-host/api/v1/solar-time \
  -H "Content-Type: application/json" \
  -H "X-API-Key: tst_your_key_here" \
  -d '{
    "date": "2026-03-30",
    "local_time": "14:30",
    "latitude": 1.3521,
    "longitude": 103.8198
  }'

GET alias (browser / shareable link)

GET /api/v1/solar-time?date=2026-03-30&local_time=14:30&latitude=1.3521&longitude=103.8198

Python (requests)

import requests

resp = requests.post(
    "https://your-host/api/v1/solar-time",
    headers={"X-API-Key": "tst_your_key_here"},
    json={
        "date": "2026-03-30",
        "local_time": "14:30",
        "place_name": "Singapore",
    },
)
resp.raise_for_status()
data = resp.json()
print(data["true_solar_time"])   # "13:51:06"
print(data["summary"])

JavaScript (fetch)

const resp = await fetch("https://your-host/api/v1/solar-time", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "tst_your_key_here",
  },
  body: JSON.stringify({
    date: "2026-03-30",
    local_time: "14:30",
    place_name: "Singapore",
  }),
});
const data = await resp.json();
console.log(data.true_solar_time); // "13:51:06"