API Documentation
Use the recurrence.dev API to expand, validate, and manipulate RFC5545 recurrence rules with sub-millisecond response times. Perfect for calendar applications, scheduling systems, and anything that needs reliable recurring events.
Quickstart
Get started in under a minute. Create an account, grab your API key, and make your first request.
Get your API key
Sign up for free and generate your API key from the dashboard.
Make a request
Call the /expand endpoint with your RRULE string.
Get occurrences
Receive an array of ISO 8601 dates back in milliseconds.
Authentication
All API requests require authentication via an API key in the Authorization header.
Authorization: Beareryour_api_key_here
Keep your API key secure
Never expose your API key in client-side code. Use environment variables and server-side requests.
Guides
RRULE Basics
Learn the fundamentals of RFC5545 recurrence rules and how to construct them.
Read more →Working with Timezones
Coming SoonHandle timezone-aware recurrence rules and DST transitions correctly.
Exclusions & Overrides
Coming SoonUse EXDATE, EXRULE, and RDATE to customize occurrence sets.
Error Handling
Understand error codes and implement robust error handling.
Read more →RRULE Basics
RRULE (Recurrence Rule) is part of the RFC5545 iCalendar specification for defining repeating events. An RRULE describes a pattern for recurring dates using a set of properties.
Common Properties
| Property | Description | Example |
|---|---|---|
FREQ |
Frequency of recurrence | DAILY, WEEKLY, MONTHLY, YEARLY |
INTERVAL |
Interval between occurrences | INTERVAL=2 (every 2nd) |
BYDAY |
Days of the week | BYDAY=MO,WE,FR |
BYMONTHDAY |
Days of the month | BYMONTHDAY=1,15 |
BYMONTH |
Months of the year | BYMONTH=1,6,12 |
BYSETPOS |
Position in the set (e.g., 2nd Tuesday) | BYSETPOS=2 or BYSETPOS=-1 (last) |
COUNT |
Total number of occurrences | COUNT=10 |
UNTIL |
End date for recurrence | UNTIL=20261231T235959Z |
Examples
FREQ=DAILY
Every day
FREQ=WEEKLY;BYDAY=MO,WE,FR
Every Monday, Wednesday, and Friday
FREQ=MONTHLY;BYDAY=TU;BYSETPOS=2
Second Tuesday of every month
FREQ=YEARLY;BYMONTH=12;BYMONTHDAY=25
Every December 25th (Christmas)
Try it out
Use the Playground to experiment with different RRULE patterns and see the generated occurrences in real-time.
/v1/expand
Expand a recurrence rule into a list of occurrence dates. This is the core endpoint for generating dates from RRULE strings.
POSThttps://api.recurrence.dev/v1/expand
Request Body (JSON)
| Parameter | Type | Required | Description |
|---|---|---|---|
rrule |
string | Yes | The RRULE string to expand |
dtstart |
string | Yes | Start date in ISO 8601 format |
limit |
integer | No | Max occurrences to return (default: 100, max: 1000) |
until |
string | No | End boundary in ISO 8601 format |
after |
string | No | Cursor for pagination (use moreItemsAfter from previous response) |
Example Request
curl -X POST "https://api.recurrence.dev/v1/expand" \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"rrule": "FREQ=WEEKLY;BYDAY=MO,WE,FR", "dtstart": "2026-01-01T09:00:00Z", "limit": 10}'
Example Response
{
"occurrences": [
"2026-01-02T09:00:00Z",
"2026-01-05T09:00:00Z",
"2026-01-07T09:00:00Z",
"2026-01-09T09:00:00Z",
"2026-01-12T09:00:00Z",
"2026-01-14T09:00:00Z",
"2026-01-16T09:00:00Z",
"2026-01-19T09:00:00Z",
"2026-01-21T09:00:00Z",
"2026-01-23T09:00:00Z"
],
"count": 10,
"moreItemsAfter": "2026-01-23T09:00:00Z"
}
Pagination
When moreItemsAfter is present, pass its value as the after parameter to fetch the next page of results.
/v1/expand/stream
Stream occurrences as newline-delimited JSON (NDJSON) for large datasets. Backed by a high-performance Rust engine capable of ~875K occurrences/second with constant memory usage.
POSThttps://api.recurrence.dev/v1/expand/stream
When to use streaming
Use /expand/stream when you need more than 1,000 occurrences or want to process results incrementally. For smaller datasets, the standard /expand endpoint with pagination is simpler. If your RRULE has a COUNT, it must be at least 1,000 to use streaming.
Request Body (JSON)
| Parameter | Type | Required | Description |
|---|---|---|---|
rrule |
string | Yes | The RRULE string to expand |
dtstart |
string | Yes | Start date in ISO 8601 format |
until |
string | No | End boundary in ISO 8601 format (recommended for infinite rules) |
batch_size |
integer | No | Occurrences per chunk: 10, 25, 50, or 100 (default: 50) |
Example Request
curl -X POST "https://api.recurrence.dev/v1/expand/stream" \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"rrule": "FREQ=DAILY", "dtstart": "2024-01-01T09:00:00Z", "until": "2024-01-05T23:59:59Z"}'
Response Format (NDJSON)
The response is streamed as newline-delimited JSON. Each line is a complete JSON object. The stream ends with a metadata line.
{"t":"2024-01-01T09:00:00Z"}
{"t":"2024-01-02T09:00:00Z"}
{"t":"2024-01-03T09:00:00Z"}
{"t":"2024-01-04T09:00:00Z"}
{"t":"2024-01-05T09:00:00Z"}
{"_meta":{"count":5,"status":"complete","units_charged":1,"quota_remaining":999}}
Client Example (JavaScript)
const response = await fetch('https://api.recurrence.dev/v1/expand/stream', {
method: 'POST',
headers: {
'Authorization': 'Bearer your_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
rrule: 'FREQ=DAILY',
dtstart: '2024-01-01T09:00:00Z',
until: '2024-12-31T23:59:59Z'
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const lines = decoder.decode(value).split('\n');
for (const line of lines) {
if (!line) continue;
const data = JSON.parse(line);
if (data._meta) {
console.log(`Complete: ${data._meta.count} occurrences`);
} else {
console.log(`Occurrence: ${data.t}`);
}
}
}
Quota counting
Streaming uses occurrence-based metering: 1,000 occurrences = 1 unit (using ceiling). For example:
- 999 occurrences = 1 unit
- 1,000 occurrences = 1 unit
- 5,432 occurrences = 6 units
The /expand endpoint always costs 1 unit per request regardless of result count.
Rate Limits
| Limit | /expand | /expand/stream |
|---|---|---|
| Requests/min | 1,000 | 100 |
| Concurrent streams | N/A | 10 per token |
Availability
Streaming is available on Developer and Enterprise plans. It is not available in the public demo API.
/v1/parse
Parse an RRULE string into its structured components. Useful for validation and understanding rule structure without generating occurrences.
POSThttps://api.recurrence.dev/v1/parse
Request Body (JSON)
| Parameter | Type | Required | Description |
|---|---|---|---|
rrule |
string | Yes | The RRULE string to parse |
Example Request
curl -X POST "https://api.recurrence.dev/v1/parse" \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"rrule": "FREQ=DAILY;INTERVAL=2;BYDAY=MO,WE,FR"}'
Example Response
{
"parsed": {
"freq": "DAILY",
"interval": 2,
"byDay": ["MO", "WE", "FR"]
},
"input": "FREQ=DAILY;INTERVAL=2;BYDAY=MO,WE,FR"
}
Error Handling
All API errors return a consistent JSON structure with an error object containing a code and message.
Error Response Format
{
"error": {
"code": "invalid_rrule",
"message": "Invalid RRULE: unknown frequency 'DAILYY'"
}
}
Error Codes
| Code | Status | Description |
|---|---|---|
missing_token |
401 | Authorization header is missing |
invalid_token |
401 | API token is invalid or expired |
quota_exhausted |
402 | Monthly quota exhausted, upgrade plan or wait for reset |
not_found |
404 | The requested resource was not found |
duplicate_stream |
409 | Stream with this ID is already in progress |
missing_param |
422 | A required parameter is missing |
invalid_rrule |
422 | The RRULE string is malformed or invalid |
invalid_datetime |
422 | The datetime value is not valid ISO 8601 format |
invalid_batch_size |
422 | The batch_size parameter is invalid (streaming endpoint) |
count_too_small_for_stream |
422 | COUNT is below 1,000; use /expand with pagination instead |
rrule_limit_exceeded |
422 | RRULE component values exceed allowed limits |
config_not_supported |
422 | RRULE configuration exceeds plan limits |
rate_limited |
429 | Too many requests/minute, check Retry-After header |
concurrent_limit |
429 | Too many concurrent streams, check max_concurrent in response |
internal_error |
500 | An unexpected server error occurred |
Rate Limits
Rate limits are enforced per API key. Limits reset at the start of each calendar month.
| Plan | Monthly Limit | Burst Rate |
|---|---|---|
| Developer | 1,000 requests | 10/sec |
| Enterprise | Custom | Custom |
Rate limit headers
Check X-RateLimit-Remaining and X-RateLimit-Reset headers to track your usage.