Recurring schedule reference
Adding a schedule object to the expense creation payload turns a one-time expense
into a recurring template. The engine generates virtual instances automatically for each month view.
Schedule object fields
| Field | Type | Default | Description |
|---|---|---|---|
frequency | string | None | Required. One of: once, daily, weekly, monthly, quarterly, yearly. |
startDate | string | integer | None | Required. ISO date ("2026-03-01") or Unix seconds (1740787200). |
endDate | string | integer | none | Hard end date. Cannot be combined with endAfter. |
dayOfMonth | integer | none | Day of month (1–31). Used with monthly / quarterly, or paired with monthOfYear for yearly. |
monthOfYear | integer | none | Month 1–12. Only for yearly, together with dayOfMonth. |
dayOfWeek | integer | none | Day of week (0 = Sunday … 6 = Saturday). Used with weekly or monthly+weekOfMonth. |
daysOfWeek | integer[] | none | Multiple days (e.g. [1, 3, 5] = Mon/Wed/Fri). weekly only. |
weekOfMonth | integer | none | Week number (1–5). Requires dayOfWeek. Used with monthly / quarterly. |
interval | integer | 1 | Repeat every N periods (1–100). E.g. 2 with weekly = every 2 weeks. |
endAfter | object | none | End condition. Cannot be combined with endDate. See below. |
timezone | string | "Europe/Warsaw" | IANA timezone for occurrence calculation. |
endAfter object
| type | value | Description |
|---|---|---|
"count" | integer (1–1000) | Stop after N total occurrences. |
"date" | ISO date or Unix seconds | Stop on/after this date. |
endDate or endAfter, but not both.
Omit both for an open-ended (infinite) recurring schedule.
Required fields by frequency
| frequency | Required extras | Allowed optional | Forbidden |
|---|---|---|---|
once | none | none | dayOfMonth, dayOfWeek, daysOfWeek, weekOfMonth, interval ≠ 1 |
daily | none | interval | dayOfMonth, dayOfWeek, daysOfWeek, weekOfMonth |
weekly | dayOfWeek or daysOfWeek | interval | dayOfMonth, weekOfMonth |
monthly | dayOfMonth or (weekOfMonth + dayOfWeek) | interval | daysOfWeek |
quarterly | dayOfMonth or (weekOfMonth + dayOfWeek) | interval | daysOfWeek |
yearly | none (legacy startDate rule) | interval, monthOfYear + dayOfMonth | daysOfWeek |
weekOfMonth and dayOfWeek.
frequency: once
A single scheduled occurrence at startDate. Useful for a future-dated expense
that should appear on its due date.
curl -X POST https://api.figa.cc/api/v1/expenses \
-H "x-api-key: et_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "Annual insurance premium",
"amount": 240000,
"categoryInput": "Insurance",
"schedule": {
"frequency": "once",
"startDate": "2026-06-15"
}
}' frequency: daily
Every day
"schedule": {
"frequency": "daily",
"startDate": "2026-03-01"
} Every 3 days
"schedule": {
"frequency": "daily",
"startDate": "2026-03-01",
"interval": 3
} Every day for 30 days
"schedule": {
"frequency": "daily",
"startDate": "2026-03-01",
"endAfter": { "type": "count", "value": 30 }
} Every day until end of year
"schedule": {
"frequency": "daily",
"startDate": "2026-03-01",
"endDate": "2026-12-31"
} frequency: weekly
Every Friday
"schedule": {
"frequency": "weekly",
"startDate": "2026-03-06",
"dayOfWeek": 5
} 0 = Sunday, 1 = Monday, 2 = Tuesday,
3 = Wednesday, 4 = Thursday, 5 = Friday,
6 = Saturday.
Every other Monday
"schedule": {
"frequency": "weekly",
"startDate": "2026-03-02",
"dayOfWeek": 1,
"interval": 2
} Monday, Wednesday, Friday every week
"schedule": {
"frequency": "weekly",
"startDate": "2026-03-02",
"daysOfWeek": [1, 3, 5]
} Tuesday and Thursday, every 2 weeks, 20 occurrences
"schedule": {
"frequency": "weekly",
"startDate": "2026-03-03",
"daysOfWeek": [2, 4],
"interval": 2,
"endAfter": { "type": "count", "value": 20 }
} Full curl: weekly team lunch
curl -X POST https://api.figa.cc/api/v1/expenses \
-H "x-api-key: et_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "Team lunch",
"amount": 15000,
"categoryInput": "Food",
"recipientInput": "Pizza Place",
"schedule": {
"frequency": "weekly",
"startDate": "2026-03-06",
"dayOfWeek": 5
}
}' frequency: monthly
1st of every month (rent, subscriptions)
"schedule": {
"frequency": "monthly",
"startDate": "2026-01-01",
"dayOfMonth": 1
} 15th of every month
"schedule": {
"frequency": "monthly",
"startDate": "2026-01-15",
"dayOfMonth": 15
} Last day of month (dayOfMonth: 31)
"schedule": {
"frequency": "monthly",
"startDate": "2026-01-31",
"dayOfMonth": 31
} dayOfMonth exceeds the number of days in a month (e.g. 31 in February),
the occurrence is placed on the last day of that month.
Every other month on the 10th
"schedule": {
"frequency": "monthly",
"startDate": "2026-01-10",
"dayOfMonth": 10,
"interval": 2
} First Monday of every month
"schedule": {
"frequency": "monthly",
"startDate": "2026-01-06",
"weekOfMonth": 1,
"dayOfWeek": 1
} Third Friday of every month
"schedule": {
"frequency": "monthly",
"startDate": "2026-01-16",
"weekOfMonth": 3,
"dayOfWeek": 5
} 12 monthly installments (loan repayment)
"schedule": {
"frequency": "monthly",
"startDate": "2026-01-01",
"dayOfMonth": 1,
"endAfter": { "type": "count", "value": 12 }
} Monthly until December 2027
"schedule": {
"frequency": "monthly",
"startDate": "2026-01-01",
"dayOfMonth": 1,
"endDate": "2027-12-31"
} Full curl: monthly rent
curl -X POST https://api.figa.cc/api/v1/expenses \
-H "x-api-key: et_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "Office rent",
"amount": 420000,
"categoryInput": "Housing",
"recipientInput": "WeWork",
"description": "Monthly office lease",
"currency": "PLN",
"schedule": {
"frequency": "monthly",
"startDate": "2026-01-01",
"dayOfMonth": 1
}
}' Full curl: 36 installments (phone lease)
curl -X POST https://api.figa.cc/api/v1/expenses \
-H "x-api-key: et_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "iPhone lease",
"amount": 12500,
"categoryInput": "Equipment",
"recipientInput": "Apple",
"schedule": {
"frequency": "monthly",
"startDate": "2026-02-15",
"dayOfMonth": 15,
"endAfter": { "type": "count", "value": 36 }
}
}' frequency: quarterly
1st of each quarter
"schedule": {
"frequency": "quarterly",
"startDate": "2026-01-01",
"dayOfMonth": 1
} 15th of each quarter
"schedule": {
"frequency": "quarterly",
"startDate": "2026-01-15",
"dayOfMonth": 15
} First Monday of each quarter
"schedule": {
"frequency": "quarterly",
"startDate": "2026-01-05",
"weekOfMonth": 1,
"dayOfWeek": 1
} Full curl: quarterly tax advance
curl -X POST https://api.figa.cc/api/v1/expenses \
-H "x-api-key: et_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "VAT advance payment",
"amount": 850000,
"categoryInput": "Tax",
"recipientInput": "Urząd Skarbowy",
"schedule": {
"frequency": "quarterly",
"startDate": "2026-01-25",
"dayOfMonth": 25
}
}' frequency: yearly
Yearly on start date anniversary
"schedule": {
"frequency": "yearly",
"startDate": "2026-06-15"
}
Without monthOfYear, the yearly month and day come from startDate;
a legacy dayOfMonth alone remains ignored. With monthOfYear (1–12),
dayOfMonth (1–31) is required and defines the yearly date. The start still
determines the interval phase and earliest generated term. The pair is available through
the same schedule schema in the session API, API-key API and existing MCP schedule edits.
Every 2 years
"schedule": {
"frequency": "yearly",
"startDate": "2026-06-15",
"interval": 2
} Preserve February 29 after extending history
{
"frequency": "yearly",
"startDate": "2023-03-01",
"monthOfYear": 2,
"dayOfMonth": 29
} This produces March 1 in 2023 and February 29 in 2024 and 2028. February 29 overflows to March 1 in non-leap years, preserving the existing yearly rule. A range backfill from a legacy February 29 anchor records this pair automatically. Reading an old schedule does not activate previously ignored fields.
Full curl: yearly domain renewal
curl -X POST https://api.figa.cc/api/v1/expenses \
-H "x-api-key: et_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "figa.io domain renewal",
"amount": 4500,
"categoryInput": "Infrastructure",
"recipientInput": "Cloudflare",
"currency": "USD",
"schedule": {
"frequency": "yearly",
"startDate": "2026-09-12"
}
}' Managing recurring expenses
To change fixed/variable monthly billing from an occurrence, use recurringSettings. Save calendar changes separately. Schedule changes are unavailable for series with billing-method history.
Read schedule details
curl -X GET https://api.figa.cc/api/v1/expenses/{expenseId} \
-H "x-api-key: et_live_abc123..."
Returns the full expense object with schedule, occurrences array,
and stats (totalOccurrences, paidOccurrences, progress, etc.).
Read a single occurrence
curl -X GET "https://api.figa.cc/api/v1/expenses/{expenseId}?occurrence=1740787200" \
-H "x-api-key: et_live_abc123..." Pass the occurrence's scheduled Unix timestamp to get that specific instance.
Change schedule for entire series
curl -X PUT https://api.figa.cc/api/v1/expenses/{expenseId} \
-H "x-api-key: et_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"schedule": {
"frequency": "monthly",
"startDate": "2026-01-05",
"dayOfMonth": 5
},
"editScope": "ALL_INSTANCES"
}' Update amount for this + future occurrences
curl -X PUT https://api.figa.cc/api/v1/expenses/{expenseId} \
-H "x-api-key: et_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"amount": 450000,
"editScope": "FUTURE_INSTANCES"
}' Override a single occurrence
curl -X PUT https://api.figa.cc/api/v1/expenses/{expenseId} \
-H "x-api-key: et_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"amount": 500000,
"description": "Increased rent for March",
"editScope": "THIS_INSTANCE"
}' Skip an occurrence
scheduledDate accepts Unix seconds or ISO date YYYY-MM-DD.
Using Unix timestamp
curl -X POST https://api.figa.cc/api/v1/expenses/{expenseId}/skip/toggle \
-H "x-api-key: et_live_abc123..." \
-H "Content-Type: application/json" \
-d '{"scheduledDate": 1740787200}' Using ISO date (recommended)
curl -X POST https://api.figa.cc/api/v1/expenses/{expenseId}/skip/toggle \
-H "x-api-key: et_live_abc123..." \
-H "Content-Type: application/json" \
-d '{"scheduledDate": "2026-03-30"}'
Call again with the same scheduledDate to restore a skipped occurrence.
Only unpaid instances can be skipped. If occurrence is not found, API returns 404.
Convert recurring to one-time
curl -X PUT https://api.figa.cc/api/v1/expenses/{expenseId} \
-H "x-api-key: et_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"schedule": null,
"editScope": "ALL_INSTANCES"
}' Delete single occurrence
curl -X DELETE "https://api.figa.cc/api/v1/expenses/{expenseId}?editScope=THIS_INSTANCE" \
-H "x-api-key: et_live_abc123..." Delete entire series
curl -X DELETE "https://api.figa.cc/api/v1/expenses/{expenseId}?editScope=ALL_INSTANCES" \
-H "x-api-key: et_live_abc123..." editScope reference
When updating (PUT) or deleting (DELETE) a recurring expense,
editScope determines which occurrences are affected:
| Value | PUT effect | DELETE effect |
|---|---|---|
THIS_INSTANCE | Materializes and overrides only this occurrence. | Removes only this occurrence. |
FUTURE_INSTANCES | Applies changes to this and all future occurrences. | Removes this and all future occurrences. |
ALL_INSTANCES | Updates the template (all past + future). | Deletes the entire recurring series. |
editScope. It is ignored for non-recurring expenses.
Quick reference: common patterns
| Use case | schedule JSON |
|---|---|
| Monthly rent on 1st | { "frequency": "monthly", "startDate": "2026-01-01", "dayOfMonth": 1 } |
| Weekly Friday coffee | { "frequency": "weekly", "startDate": "2026-01-02", "dayOfWeek": 5 } |
| Quarterly tax (25th) | { "frequency": "quarterly", "startDate": "2026-01-25", "dayOfMonth": 25 } |
| Yearly renewal | { "frequency": "yearly", "startDate": "2026-09-12" } |
| Bi-weekly Mon payroll | { "frequency": "weekly", "startDate": "2026-01-05", "dayOfWeek": 1, "interval": 2 } |
| Mon/Wed/Fri gym | { "frequency": "weekly", "startDate": "2026-01-05", "daysOfWeek": [1, 3, 5] } |
| 1st Monday of month | { "frequency": "monthly", "startDate": "2026-01-05", "weekOfMonth": 1, "dayOfWeek": 1 } |
| 12 installments | { "frequency": "monthly", "startDate": "2026-01-01", "dayOfMonth": 1, "endAfter": { "type": "count", "value": 12 } } |
| Daily until end of year | { "frequency": "daily", "startDate": "2026-01-01", "endDate": "2026-12-31" } |
| Every 3 days, 30 times | { "frequency": "daily", "startDate": "2026-01-01", "interval": 3, "endAfter": { "type": "count", "value": 30 } } |