Skip to content
Zeitwide

API reference

Zeitwide API 1.0.0. Generated from the OpenAPI contract, so it always matches the API.

calendars

GET/v1/calendars

Every calendar, with its rules, revision and coverage

Needs a Keycloak token or an API key.

Parameters for listCalendars
ParameterInTypeNotes
as_ofquerystring (date-time)Answer with what Zeitwide had recorded at this instant. Default now.
  • 200 The calendars in the dataset release. CalendarList
  • 400 `validation_failed`: the request is malformed.
  • 401 `invalid_api_key`: the key is unknown, malformed or revoked.
  • 429 `rate_limited`: too many requests; try again after `Retry-After` seconds.
  • 503 `dataset_unavailable`: no dataset release has been cut yet.

GET/v1/calendars/{id}/days/{date}

Whether a date is a business day on one calendar, and why

Needs a Keycloak token or an API key.

Parameters for getDay
ParameterInTypeNotes
id *pathCalendarId
date *pathstring (date)
as_ofquerystring (date-time)Answer with what Zeitwide had recorded at this instant. Default now.
  • 200 The answer and the entry or rule that decided it. DayAnswer
  • 400 `validation_failed`: the request is malformed.
  • 401 `invalid_api_key`: the key is unknown, malformed or revoked.
  • 404 `unknown_calendar`: no calendar has this ID.
  • 422 `out_of_range`: a date is outside a calendar's source coverage.
  • 429 `rate_limited`: too many requests; try again after `Retry-After` seconds.
  • 503 `dataset_unavailable`: no dataset release has been cut yet.

GET/v1/calendars/{id}/days

Answer every date in a range for one calendar

Each covered date from `from` to `to` (both inclusive, at most 500 days), answered as `GET /v1/calendars/{id}/days/{date}` would, with the nearest business days either side. Dates outside the calendar's coverage are left out. With no `from`, the range starts on 1 January of the current year; with no `to`, it ends 90 days after today. Both use the calendar's time zone, and `today` is the calendar's date now.

Needs a Keycloak token or an API key.

Parameters for listDays
ParameterInTypeNotes
id *pathCalendarId
fromquerystring (date)
toquerystring (date)
as_ofquerystring (date-time)Answer with what Zeitwide had recorded at this instant. Default now.
  • 200 The covered days in the range, in date order. DayRange
  • 400 `validation_failed`: the request is malformed.
  • 404 `unknown_calendar`: no calendar has this ID.
  • 429 `rate_limited`: too many requests; try again after `Retry-After` seconds.
  • 503 `dataset_unavailable`: no dataset release has been cut yet.

GET/v1/calendars/{id}/closures

The closures on one calendar between two dates

Closures are the dates an entry closes (weekends aren't listed). The range is at most 731 days, and all of it must be within the calendar's coverage.

Needs a Keycloak token or an API key.

Parameters for listClosures
ParameterInTypeNotes
id *pathCalendarId
from *querystring (date)
to *querystring (date)Inclusive; on or after `from`.
as_ofquerystring (date-time)Answer with what Zeitwide had recorded at this instant. Default now.
  • 200 The closures in date order. ClosureList
  • 400 `validation_failed`: the request is malformed.
  • 401 `invalid_api_key`: the key is unknown, malformed or revoked.
  • 404 `unknown_calendar`: no calendar has this ID.
  • 422 `out_of_range`: a date is outside a calendar's source coverage.
  • 429 `rate_limited`: too many requests; try again after `Retry-After` seconds.
  • 503 `dataset_unavailable`: no dataset release has been cut yet.

business days

POST/v1/business-days/check

Whether a date is a business day on several calendars combined

Needs a Keycloak token or an API key.

Parameters for checkBusinessDay
ParameterInTypeNotes
as_ofquerystring (date-time)Answer with what Zeitwide had recorded at this instant. Default now.

Body: DayRequest

  • 200 The combined answer, with each calendar's answer as evidence. DayAnswer
  • 400 `validation_failed`: the request is malformed.
  • 401 `invalid_api_key`: the key is unknown, malformed or revoked.
  • 404 `unknown_calendar`: no calendar has this ID.
  • 422 `out_of_range`: a date is outside a calendar's source coverage.
  • 429 `rate_limited`: too many requests; try again after `Retry-After` seconds.
  • 503 `dataset_unavailable`: no dataset release has been cut yet.

POST/v1/business-days/add

Move a number of business days from a date

Moves `days` business days from `date`, backwards when negative. `date` itself doesn't count, and 0 returns `date`. Every day passed over is listed in `skipped`, in the order met, with why it isn't a business day.

Needs a Keycloak token or an API key.

Parameters for addBusinessDays
ParameterInTypeNotes
as_ofquerystring (date-time)Answer with what Zeitwide had recorded at this instant. Default now.

Body: AddRequest

  • 200 Where the move landed. Shifted
  • 400 `validation_failed`: the request is malformed.
  • 401 `invalid_api_key`: the key is unknown, malformed or revoked.
  • 404 `unknown_calendar`: no calendar has this ID.
  • 422 `out_of_range`: a date is outside a calendar's source coverage.
  • 429 `rate_limited`: too many requests; try again after `Retry-After` seconds.
  • 503 `dataset_unavailable`: no dataset release has been cut yet.

POST/v1/business-days/next

The first business day after a date

Never `date` itself; the same as adding one business day.

Needs a Keycloak token or an API key.

Parameters for nextBusinessDay
ParameterInTypeNotes
as_ofquerystring (date-time)Answer with what Zeitwide had recorded at this instant. Default now.

Body: DayRequest

  • 200 The next business day. Shifted
  • 400 `validation_failed`: the request is malformed.
  • 401 `invalid_api_key`: the key is unknown, malformed or revoked.
  • 404 `unknown_calendar`: no calendar has this ID.
  • 422 `out_of_range`: a date is outside a calendar's source coverage.
  • 429 `rate_limited`: too many requests; try again after `Retry-After` seconds.
  • 503 `dataset_unavailable`: no dataset release has been cut yet.

changes

GET/v1/changes

List published calendar changes using keyset pagination

Needs a Keycloak token or an API key.

Parameters for listChanges
ParameterInTypeNotes
calendarsqueryarray of CalendarIdUp to 10 calendar IDs; omit to include all calendars.
since *querystring (date-time)
typesqueryarray of ChangeTypeChange types to include; omit to include all types.
cursorquerystring
limitqueryinteger
  • 200 Changes ordered by publication instant and stable event ID. ChangePage
  • 400 `validation_failed`: the request is malformed.
  • 429 `rate_limited`: too many requests; try again after `Retry-After` seconds.

receipts

POST/v1/receipts/verify

Verify a receipt against the configured public key history

Needs a Keycloak token or an API key.

Body: Receipt

  • 200 `valid` is false for a modified payload, signature, ID or unknown key. ReceiptVerification
  • 400 `validation_failed`: the request is malformed.

GET/.well-known/zeitwide-keys.json

Public Ed25519 verification keys and validity intervals

Open to anyone, no key needed.

  • 200 Current and retained historical public keys. ReceiptKeySet

mcp

GET/.well-known/oauth-protected-resource/mcp

OAuth protected resource metadata for the MCP endpoint

Open to anyone, no key needed.

  • 200 MCP resource identifier, authorization server, and supported bearer method. object

account

GET/v1/account/api-keys

List API keys for the authenticated account

Needs a Keycloak token or an API key.

  • 200 Up to 100 key metadata rows. Secret values are never returned by this operation. array of ApiKeySummary
  • 401 `invalid_api_key`: the key is unknown, malformed or revoked.

POST/v1/account/api-keys

Create an API key and return its value once

Needs a Keycloak token or an API key.

Parameters for createApiKey
ParameterInTypeNotes
Idempotency-Key *headerstringA 16–128 character key. Reuse is rejected so a retry cannot create another secret.

Body: object

  • 201 The key value is present only in this response. CreatedApiKey
  • 400 `validation_failed`: the request is malformed.
  • 401 `invalid_api_key`: the key is unknown, malformed or revoked.
  • 409 API key account limit reached or this idempotency key was already used.

DELETE/v1/account/api-keys/{id}

Revoke one API key belonging to the authenticated account

Needs a Keycloak token or an API key.

Parameters for revokeApiKey
ParameterInTypeNotes
id *pathstring (uuid)
  • 204 The key was revoked or was already absent.
  • 401 `invalid_api_key`: the key is unknown, malformed or revoked.

GET/v1/account/webhooks

List webhook subscriptions without their signing secrets

Needs a Keycloak token or an API key.

  • 200 Up to 100 active subscriptions for the authenticated account. array of WebhookSubscription
  • 401 `unauthorized`: authentication is required.

POST/v1/account/webhooks

Create a webhook and return its signing secret once

Needs a Keycloak token or an API key.

Parameters for createWebhook
ParameterInTypeNotes
Idempotency-Key *headerstring

Body: WebhookConfiguration

  • 201 The generated signing secret is returned only in this response. CreatedWebhook
  • 400 `validation_failed`: the request is malformed.
  • 401 `unauthorized`: authentication is required.
  • 409 `idempotency_key_reused`: this key was already used.
  • 429 `rate_limited`: too many requests; try again after `Retry-After` seconds.

PUT/v1/account/webhooks/{id}

Update a webhook URL and event filter

Needs a Keycloak token or an API key.

Parameters for updateWebhook
ParameterInTypeNotes
id *pathstring (uuid)

Body: WebhookConfiguration

  • 200 The updated subscription; its signing secret remains hidden. WebhookSubscription
  • 400 `validation_failed`: the request is malformed.
  • 404 `not_found`: no active resource has this ID.

DELETE/v1/account/webhooks/{id}

Disable a webhook and abandon its pending deliveries

Needs a Keycloak token or an API key.

Parameters for deleteWebhook
ParameterInTypeNotes
id *pathstring (uuid)
  • 204 The subscription was disabled or was already absent.

POST/v1/account/webhooks/{id}/test

Send a signed test event to a webhook

Needs a Keycloak token or an API key.

Parameters for sendWebhookTestEvent
ParameterInTypeNotes
id *pathstring (uuid)
  • 200 Test event delivery result; receiver response bodies are discarded. WebhookTestResult
  • 404 `not_found`: no active resource has this ID.

GET/v1/account/alert-rules

List the account's alert rules

Alert rules say which published changes matter to the account. While an account has at least one rule, its webhooks receive only changes that match a rule (and the webhook's own event filter); with no rules, webhooks receive every change.

Needs a Keycloak token or an API key.

  • 200 Up to 50 rules, newest first. array of AlertRule
  • 401 `unauthorized`: authentication is required.

POST/v1/account/alert-rules

Create an alert rule

Needs a Keycloak token or an API key.

Parameters for createAlertRule
ParameterInTypeNotes
Idempotency-Key *headerstring

Body: AlertRuleConfiguration

  • 201 The created rule. AlertRule
  • 400 `validation_failed`: the request is malformed.
  • 401 `unauthorized`: authentication is required.
  • 409 `alert_rule_limit_reached` or `idempotency_key_reused`.

PUT/v1/account/alert-rules/{id}

Replace an alert rule

Needs a Keycloak token or an API key.

Parameters for updateAlertRule
ParameterInTypeNotes
id *pathstring (uuid)

Body: AlertRuleConfiguration

  • 200 The updated rule. AlertRule
  • 400 `validation_failed`: the request is malformed.
  • 404 `not_found`: no active resource has this ID.

DELETE/v1/account/alert-rules/{id}

Delete an alert rule

Needs a Keycloak token or an API key.

Parameters for deleteAlertRule
ParameterInTypeNotes
id *pathstring (uuid)
  • 204 The rule was deleted or was already absent.

admin

GET/v1/admin/candidates

List candidate changes for review, urgent first

Pending candidates sort urgent first (under 14 days' notice), then by date. Requires the Keycloak role `reviewer`.

Needs a Keycloak token or an API key.

Parameters for listCandidates
ParameterInTypeNotes
statusqueryCandidateStatus
limitqueryinteger
  • 200 Candidates with the requested status. CandidateList
  • 401 `unauthorized`: authentication is required.
  • 403 `forbidden`: the caller lacks the required role.

GET/v1/admin/candidates/{id}

One candidate with the current entry and every supporting snapshot

Needs a Keycloak token or an API key.

Parameters for getCandidate
ParameterInTypeNotes
id *pathinteger (int64)
  • 200 The candidate and its evidence. CandidateDetail
  • 401 `unauthorized`: authentication is required.
  • 403 `forbidden`: the caller lacks the required role.
  • 404 `not_found`: no active resource has this ID.

POST/v1/admin/candidates/{id}/decision

Approve or reject a pending candidate

Records the reviewer (the token's subject), the reason and the time. The pipeline publishes an approved candidate on its next run, in one transaction; no answer changes before then.

Needs a Keycloak token or an API key.

Parameters for decideCandidate
ParameterInTypeNotes
id *pathinteger (int64)

Body: CandidateDecision

  • 200 The candidate after the decision. CandidateDetail
  • 400 `validation_failed`: the request is malformed.
  • 401 `unauthorized`: authentication is required.
  • 403 `forbidden`: the caller lacks the required role.
  • 404 `not_found`: no active resource has this ID.
  • 409 `candidate_not_pending`: someone already decided this candidate.

GET/v1/admin/sources

Freshness and failures for every source

Needs a Keycloak token or an API key.

  • 200 Every source with its check state. SourceHealthList
  • 401 `unauthorized`: authentication is required.
  • 403 `forbidden`: the caller lacks the required role.

GET/v1/admin/releases

Dataset releases, newest first

Needs a Keycloak token or an API key.

Parameters for listReleases
ParameterInTypeNotes
limitqueryinteger
  • 200 The most recent releases. ReleaseList
  • 401 `unauthorized`: authentication is required.
  • 403 `forbidden`: the caller lacks the required role.

Schemas

AddRequest

Fields of AddRequest
FieldTypeNotes
calendars *array of CalendarId
date *string (date)
days *integer
combineCombine
as_ofstring (date-time)

AlertRule

Fields of AlertRule
FieldTypeNotes
id *string (uuid)
name *string
calendars *array of CalendarId
events *array of ChangeType
max_notice_days *integer or null
created_at *string (date-time)

AlertRuleConfiguration

Fields of AlertRuleConfiguration
FieldTypeNotes
name *string
calendars *array of CalendarId
events *array of ChangeType
max_notice_daysinteger or nullOnly changes announced this many days or fewer ahead; null for any notice.

ApiKeySummary

Fields of ApiKeySummary
FieldTypeNotes
id *string (uuid)
name *string
prefix *string
created_at *string (date-time)
last_used_atstring or null (date-time)
revoked_atstring or null (date-time)

Basis

`published` by the operator, or `projected` by Zeitwide from other sources.

published | projected

Calendar

Fields of Calendar
FieldTypeNotes
id *CalendarId
name *string
kind *rail | public | exchange
timezone *string
weekend *array of monday | tuesday | wednesday | thursday | friday | saturday | sunday
closes_on_holidays *boolean
revision *integer
coverage *Coverage

CalendarDay

One calendar's answer for one date, and what decided it.

Fields of CalendarDay
FieldTypeNotes
calendar_id *CalendarId
is_open *boolean
decided_by *entry | weekend_ruleAn entry (with its reason, basis and sources), or the weekend rule.
reasonstringOnly when an entry decided.
basisBasis
revisionintegerThe calendar revision that published the entry.
recorded_fromstring (date-time)
sources *array of Source

CalendarId

string

CalendarList

Fields of CalendarList
FieldTypeNotes
calendars *array of Calendar
as_of *string (date-time)
dataset_release *DatasetRelease
calendar_revisions *CalendarRevisions
receipt *Receipt

CalendarRevisions

Each calendar's revision at `as_of`, by calendar ID.

object

Candidate

Fields of Candidate
FieldTypeNotes
id *integer (int64)
calendar_id *CalendarId
entry_date *string (date)
before *EntryState or nullThe published state when the candidate was opened; null for a new date.
after *EntryState
corroboration *Corroboration
source_count *integer
notice_days *integerDays from today (UTC) to the date; negative once it has passed.
urgent *booleanUnder 14 days' notice.
status *CandidateStatus
created_at *string (date-time)

CandidateDecision

Fields of CandidateDecision
FieldTypeNotes
decision *approve | reject
reason *string

CandidateDetail

Fields of CandidateDetail
FieldTypeNotes
candidate *Candidate
current *EntryState or nullThe entry published now, if any.
snapshots *array of CandidateSnapshot
reviewer *string or null
review_reason *string or null
reviewed_at *string or null (date-time)

CandidateList

Fields of CandidateList
FieldTypeNotes
candidates *array of Candidate

CandidateSnapshot

One source snapshot that supports the candidate, and what the parser read from it.

Fields of CandidateSnapshot
FieldTypeNotes
source *stringThe source's slug.
publisher *string
url *string (uri)
role *primary | corroborating
fetched_at *string (date-time)
snapshot_sha256 *string
http_status *integer
content_type *string or null
byte_size *integer
is_open *boolean
reason *string
parser_version *string

CandidateStatus

pending | approved | rejected | published

Change

Fields of Change
FieldTypeNotes
id *integer (int64)
type *ChangeType
calendar_id *CalendarId
entry_date *string (date)
beforeobject or null
after *object
notice_days *integer
published_at *string (date-time)

ChangePage

Fields of ChangePage
FieldTypeNotes
changes *array of Change
next_cursor *string or null
since *string (date-time)
limit *integer

ChangeType

closure_added | closure_removed | closure_changed | basis_changed

Closure

Fields of Closure
FieldTypeNotes
date *string (date)
reason *string
basis *Basis
revision *integer
recorded_from *string (date-time)
sources *array of Source

ClosureList

Fields of ClosureList
FieldTypeNotes
calendar_id *CalendarId
from *string (date)
to *string (date)
closures *array of Closure
as_of *string (date-time)
dataset_release *DatasetRelease
calendar_revision *integer
calendar_revisions *CalendarRevisions
receipt *Receipt

Combine

`all_open`: every calendar is open. `any_open`: at least one is.

all_open | any_open

Corroboration

`corroborated`: a second, independent source agrees.

single_source | corroborated

Coverage

The dates the calendar can answer for. `years`: from `from` to `to`, both inclusive. `unlimited`: any date (the calendar closes on no holiday). `none`: nothing recorded yet, so no date.

Fields of Coverage
FieldTypeNotes
type *years | unlimited | none
fromstring (date)Only for `years`.
tostring (date)Only for `years`.

CreatedApiKey

Fields of CreatedApiKey
FieldTypeNotes
id *string (uuid)
name *string
prefix *string
key *string
created_at *string (date-time)

CreatedWebhook

WebhookSubscription and object

DatasetRelease

string

DayAnswer

Fields of DayAnswer
FieldTypeNotes
date *string (date)
is_business_day *boolean
combine *Combine
calendars *array of CalendarDay
as_of *string (date-time)
dataset_release *DatasetRelease
calendar_revisions *CalendarRevisions
receipt *Receipt

DayRange

Fields of DayRange
FieldTypeNotes
calendar_id *CalendarId
today *string (date)The calendar's date now
from *string (date)
to *string (date)
days *array of RangeDay
as_of *string (date-time)
dataset_release *DatasetRelease
calendar_revisions *CalendarRevisions
receipt *Receipt

DayRequest

Fields of DayRequest
FieldTypeNotes
calendars *array of CalendarId
date *string (date)
combineCombine
as_ofstring (date-time)

EntryState

Fields of EntryState
FieldTypeNotes
is_open *boolean
reason *string
basisBasis

OutboxDrainResult

Fields of OutboxDrainResult
FieldTypeNotes
outbox_events *integer
claimed *integer
succeeded *integer
retried *integer
failed *integer

Problem

RFC 9457 problem details.

Fields of Problem
FieldTypeNotes
typestring (uri-reference)
title *string
status *integer
detailstring
instancestring (uri-reference)
code *validation_failed | invalid_api_key | unauthorized | forbidden | length_required | payload_too_large | api_key_limit_reached | idempotency_key_reused | webhook_limit_reached | alert_rule_limit_reached | candidate_not_pending | unknown_calendar | out_of_range | rate_limited | dataset_unavailable | not_found | internal_error

RangeDay

Fields of RangeDay
FieldTypeNotes
date *string (date)
is_open *boolean
decided_by *entry | weekend_rule
reasonstringOnly when an entry decided.
basisBasis
sources *array of SourceThe snapshots behind the entry; empty when the weekend rule decided.
next_business_daystring (date)The first business day after `date`; absent if it is past coverage.
previous_business_daystring (date)The last business day before `date`; absent if it is before coverage.

Receipt

Fields of Receipt
FieldTypeNotes
id *string
payload *ReceiptPayload
payload_hash *string
key_id *string
signature *string

ReceiptKeySet

Fields of ReceiptKeySet
FieldTypeNotes
keys *array of ReceiptPublicKey

ReceiptPayload

Fields of ReceiptPayload
FieldTypeNotes
request *object
answer *object
dataset_release *DatasetRelease
calendar_revisions *CalendarRevisions
issued_at *string (date-time)

ReceiptPublicKey

Fields of ReceiptPublicKey
FieldTypeNotes
key_id *string
public_key *stringPEM-encoded Ed25519 SubjectPublicKeyInfo.
valid_from *string (date-time)
valid_to *string or null (date-time)

ReceiptVerification

Fields of ReceiptVerification
FieldTypeNotes
valid *boolean

Release

Fields of Release
FieldTypeNotes
id *DatasetRelease
as_of *string (date-time)
calendar_revisions *CalendarRevisions
export_sha256 *string
created_at *string (date-time)

ReleaseList

Fields of ReleaseList
FieldTypeNotes
releases *array of Release

Shifted

Fields of Shifted
FieldTypeNotes
start *string (date)
days *integer
date *string (date)Where the move landed.
combine *Combine
calendars *array of CalendarDayEach calendar's answer for `date`.
skipped *array of SkippedDayDays passed over because they weren't business days, in the order met.
as_of *string (date-time)
dataset_release *DatasetRelease
calendar_revisions *CalendarRevisions
receipt *Receipt

SkippedDay

Fields of SkippedDay
FieldTypeNotes
date *string (date)
calendars *array of CalendarDay

Source

A snapshot of an official source that backs an entry.

Fields of Source
FieldTypeNotes
publisher *string
url *string (uri)
retrieved_at *string (date-time)
snapshot_sha256 *string

SourceHealth

Fields of SourceHealth
FieldTypeNotes
source *string
publisher *string
url *string (uri)
role *primary | corroborating
schedule *string
enabled *boolean
health *healthy | failing | suspended | disabled | never_checked`failing`: the last check failed; `suspended`: stopped after repeated failures.
consecutive_failures *integer
last_checked_at *string or null (date-time)
last_success_at *string or null (date-time)
last_snapshot_at *string or null (date-time)
last_snapshot_sha256 *string or null
last_error *string or null
suspended_at *string or null (date-time)

SourceHealthList

Fields of SourceHealthList
FieldTypeNotes
sources *array of SourceHealth

WebhookConfiguration

Fields of WebhookConfiguration
FieldTypeNotes
url *string (uri)HTTPS URL on a public host using port 443.
events *array of ChangeType

WebhookSubscription

Fields of WebhookSubscription
FieldTypeNotes
id *string (uuid)
url *string (uri)
events *array of ChangeType
created_at *string (date-time)

WebhookTestResult

Fields of WebhookTestResult
FieldTypeNotes
succeeded *boolean
response_status *integer or null