---
title: "RRULE expander: RFC 5545 recurrence rule to concrete occurrence dates"
method: GET
path: "/v1/time/rrule"
tags: ["deterministic", "no-api-key", "time", "calendar", "recurrence"]
---

# RRULE expander: RFC 5545 recurrence rule to concrete occurrence dates

`GET /v1/time/rrule`

## Request

- Base URL: `https://api.duoleads.com`
- URL: `https://api.duoleads.com/v1/time/rrule`
- Auth: none declared

RRULE expander: expand an RFC 5545 recurrence rule into concrete occurrence timestamps. Accepts an RRULE string with DTSTART, an IANA time zone, and optional EXDATE and RDATE lists, and returns up to 200 occurrences as both local wall-clock times and UTC instants. Supports FREQ, INTERVAL, COUNT, UNTIL, BYDAY, BYMONTHDAY, BYMONTH, BYSETPOS and WKST. Occurrences moved by a DST gap carry gap: true. Rules needing over 50,000 candidate evaluations are rejected.

**Output (`data`):** occurrences [{local, instant, epoch_ms, offset, dst, gap (present, true, only when a DST gap shifted the occurrence forward)}], count, truncated, rule (the parsed rule: freq, interval (integer), wkst, and, when given, count, until, byday [], bymonthday [], bymonth [], bysetpos [], byhour [], byminute [], bysecond []), zone.

Deterministic: identical input returns identical output, so the response is served with `Cache-Control: private, max-age=31536000, immutable` (the paying client may cache it indefinitely; shared caches must not, because the response is paid).

Price: $0.05 USDC per call (x402, Base). Route contract version 1.0.0. Docs: https://api.duoleads.com/docs#time-rrule

## Query parameters

- `rrule` string, required
- `dtstart` string, required
- `zone` string, required
- `count` integer
- `after` string
- `exdate` string
- `rdate` string

## Response `200`

Paid and settled. `data` is route-specific; a route that judges its input returns 200 with `data.valid: false` for a bad value.

- object
  - `ok` unknown, required
  - `route` '/v1/time/rrule', required
  - `version` '1.0.0', required
  - `data` object, required: occurrences [{local, instant, epoch_ms, offset, dst, gap (present, true, only when a DST gap shifted the occurrence forward)}], count, truncated, rule (the parsed rule: freq, interval (integer), wkst, and, when given, count, until, byday [], bymonthday [], bymonth [], bysetpos [], byhour [], byminute [], bysecond []), zone.
    - `occurrences` object[], required
      - `local` string, required
      - `instant` string, required
      - `epoch_ms` integer, required
      - `offset` string, required
      - `dst` boolean, required
      - `gap` boolean
    - `count` integer, required
    - `truncated` boolean, required
    - `rule` object, required
      - `freq` string, required
      - `interval` integer, required
      - `wkst` string, required
      - `count` integer
      - `byday` string[]
      - `until` string
      - `bymonthday` integer[]
      - `bymonth` integer[]
      - `bysetpos` integer[]
      - `byhour` integer[]
      - `byminute` integer[]
      - `bysecond` integer[]
    - `zone` string, required
  - `meta` Meta, required
    - `disclaimer` string, required: Legal short disclaimer, identical on every response.
    - `request_id` string, required: ULID.
    - `computed_at` string, date-time, required: RFC 3339 UTC with milliseconds.
    - `price_usd` string, required: Decimal string, "0.05".
    - `deterministic` boolean, required
    - `sources` object[]: Present only on routes that read a vendored dataset: the exact edition that answered.
      - `id` string, required
      - `version` string, required
      - `published` string, required
      - `attribution` string
      - `licence` string

## Other responses

- `400`: MISSING_PARAM, INVALID_PARAM or UNSUPPORTED_VALUE. Not charged: validation runs before the paywall.
- `402`: Payment required. No valid PAYMENT-SIGNATURE was supplied: read the base64 PAYMENT-REQUIRED header (or this body) for the price ($0.05 = 50000 atomic USDC on eip155:8453), sign an EIP-3009 authorization and retry. Also returned when settlement is declined (PAYMENT_FAILED). Nothing is charged on a 402.
- `404`: NOT_FOUND: unknown path. Not charged.
- `405`: METHOD_NOT_ALLOWED: only GET and HEAD are served. Not charged.
- `413`: INPUT_TOO_LARGE: a parameter exceeded its documented maximum length. Not charged.
- `500`: INTERNAL: a server bug. Not charged.

---

[API](https://skmtc.dev/duoleads/apis/duo-data-utilities-iban-isin-vat-tax-id-barcode-timezone-geo.md) · [All operations](https://skmtc.dev/duoleads/apis/duo-data-utilities-iban-isin-vat-tax-id-barcode-timezone-geo/llms.txt) · [OpenAPI document](https://skmtc.dev/duoleads/apis/duo-data-utilities-iban-isin-vat-tax-id-barcode-timezone-geo/revisions/18790d59615e?raw)
