Skip to main content

Scheduled Functions

Automate recurring tasks, maintenance operations, and time-based workflows with ekoDB's built-in scheduler.

Background Execution

Scheduled functions run automatically in the background without blocking database operations.

Quick Start

# 1. Create a function to schedule
curl -X POST https://{EKODB_API_URL}/api/functions \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"label": "daily_cleanup",
"description": "Remove old log records",
"functions": [{
"type": "Delete",
"collection": "logs",
"filter": {
"type": "Condition",
"content": {
"field": "created_at",
"operator": "Lt",
"value": "{{cutoff_date}}"
}
}
}]
}'

# 2. Schedule it to run daily at midnight
curl -X POST https://{EKODB_API_URL}/api/schedules \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Daily Log Cleanup",
"function_label": "daily_cleanup",
"cron_expression": "0 0 0 * * *",
"parameters": {"cutoff_date": "2026-01-01T00:00:00Z"},
"enabled": true,
"timezone": "UTC"
}'

Core Concepts

Cron Expressions

ekoDB uses 6-field cron expressions:

┌───────────── second (0-59)
│ ┌───────────── minute (0-59)
│ │ ┌───────────── hour (0-23)
│ │ │ ┌───────────── day of month (1-31)
│ │ │ │ ┌───────────── month (1-12 or JAN-DEC)
│ │ │ │ │ ┌───────────── day of week (0-6 or SUN-SAT)
│ │ │ │ │ │
* * * * * *

Common Schedules

ExpressionDescriptionRuns At
0 * * * * *Every minute:00 seconds of every minute
0 0 * * * *Every hourTop of every hour
0 0 0 * * *Daily at midnight00:00:00 every day
0 0 9 * * MON-FRIWeekdays at 9 AM9:00 AM Monday-Friday
0 0 0 1 * *Monthly on 1stMidnight on 1st of month
0 */15 * * * *Every 15 minutes:00, :15, :30, :45 of each hour
0 30 2 * * *Daily at 2:30 AM2:30 AM every day

Special Characters

  • * - Any value (every)
  • , - Value list separator (1,15,30)
  • - - Range (MON-FRI, 9-17)
  • / - Step values (*/15 = every 15 units)

Creating Schedules

Step 1: Create a Function

First, create the function you want to schedule:

curl -X POST https://{EKODB_API_URL}/api/functions \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"label": "generate_report",
"description": "Generate daily analytics report",
"functions": [
{
"type": "Query",
"collection": "events",
"filter": {
"type": "Condition",
"content": {
"field": "timestamp",
"operator": "Gt",
"value": "{{start_date}}"
}
}
},
{
"type": "Group",
"by_fields": ["event_type"],
"functions": [{ "output_field": "total", "operation": "Count" }]
},
{
"type": "ForEach",
"functions": [
{
"type": "Insert",
"collection": "reports",
"record": {
"event_type": "{{event_type}}",
"count": "{{total}}",
"since": "{{start_date}}"
}
}
]
}
]
}'

This actually persists the report: Query loads the events, Group produces one record per event_type shaped like { "event_type": "click", "total": 12 } (the grouping key is stored under the by-field name), and ForEach iterates those grouped records — exposing each record's fields as {{event_type}} and {{total}} placeholders — so the nested Insert writes one row per group into reports. {{start_date}} is the schedule parameter, available in every stage.

Persisting computed/aggregated results

A stage like Insert writes the literal record in its definition; it does not automatically carry the pipeline's computed data. To persist what an earlier stage produced (a Query/Group/Project result), wrap the write in ForEach and reference the current record's fields with text {{field}} placeholders. Use the text form (not the structural { "type": "Parameter", "name": "field" } form) for ForEach-injected fields: they are resolved per iteration, whereas structural parameters must be declared function parameters and are rejected by the save-time validator.

Step 2: Schedule the Function

curl -X POST https://{EKODB_API_URL}/api/schedules \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Daily Analytics Report",
"description": "Generate analytics every day at 1 AM",
"function_label": "generate_report",
"cron_expression": "0 0 1 * * *",
"parameters": {
"start_date": "2026-01-01T00:00:00Z"
},
"enabled": true,
"timezone": "UTC"
}'

# Response includes schedule id and next_execution timestamp
Schedule parameters are passed verbatim

The parameters object is stored as-is and passed to the function unchanged on every run — there is no execution-time {{NOW}}/current-time substitution. Do not put a fixed timestamp in parameters expecting it to track each run. To record when a run executed, stamp it inside the function with a CurrentDatetime stage (which writes the current UTC time onto the working record) rather than passing it as a parameter.

Cron is evaluated in the schedule's timezone

The cron expression is evaluated in the schedule's timezone (calculate_next_execution resolves the next occurrence in that zone, then stores it as an absolute UTC instant). The timezone accepts any IANA name (e.g. UTC, America/New_York, Europe/London) and defaults to UTC. Daylight-saving transitions are handled automatically, so a 0 0 9 * * * schedule in America/New_York fires at 9am New York wall-clock time year round. An invalid timezone is rejected at create/update time.

Managing Schedules

List All Schedules

curl -X GET https://{EKODB_API_URL}/api/schedules \
-H "Authorization: Bearer {TOKEN}"

Get Schedule by ID

curl -X GET https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}"

Update Schedule

# Change schedule time
curl -X PUT https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"cron_expression": "0 0 2 * * *"
}'

# Update parameters
curl -X PUT https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"parameters": {
"start_date": "2026-06-01T00:00:00Z"
}
}'

Enable/Disable Schedule

# Disable temporarily
curl -X PUT https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'

# Re-enable
curl -X PUT https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{"enabled": true}'

Delete Schedule

curl -X DELETE https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}"

Execution & Monitoring

Execution Lifecycle

  1. Schedule Check (every 60 seconds)

    • ScheduleManager loads all enabled schedules
    • Calculates if next_execution <= now
  2. Function Execution

    • Calls the associated function with parameters
    • Measures execution time
    • Updates last_execution timestamp
  3. Statistics Update

    • Increments total_executions
    • Updates success/failure counts
    • Calculates average execution time
    • Logs errors if execution fails
  4. Next Execution Calculation

    • Determines next run based on cron expression
    • Updates next_execution field

Execution Statistics

curl -X GET https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}"

# Response includes stats object:
# {
# "stats": {
# "total_executions": 42,
# "successful_executions": 41,
# "failed_executions": 1,
# "avg_execution_time_ms": 125.3,
# "last_error": null
# }
# }

You can also retrieve aggregate stats across all schedules:

curl -X GET https://{EKODB_API_URL}/api/schedules/stats \
-H "Authorization: Bearer {TOKEN}"

Error Handling

When a scheduled function fails, check the schedule's stats.last_error field:

curl -X GET https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}"

# Check stats.failed_executions and stats.last_error in the response

Real-World Use Cases

Data Cleanup

# Remove old records daily at 3 AM
curl -X POST https://{EKODB_API_URL}/api/schedules \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Delete Old Logs",
"function_label": "cleanup_old_logs",
"cron_expression": "0 0 3 * * *",
"parameters": {"days_to_keep": 30},
"enabled": true
}'

Periodic Reports

# Generate weekly summary on Sundays at 9 AM
curl -X POST https://{EKODB_API_URL}/api/schedules \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Weekly Summary",
"function_label": "generate_weekly_summary",
"cron_expression": "0 0 9 * * SUN",
"parameters": {"email_to": "team@company.com"},
"enabled": true
}'

Data Synchronization

# Sync with external API every hour
curl -X POST https://{EKODB_API_URL}/api/schedules \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Sync External Data",
"function_label": "sync_external_api",
"cron_expression": "0 0 * * * *",
"parameters": {"api_endpoint": "https://api.example.com/data"},
"enabled": true
}'

Cache Warming

# Pre-load frequently accessed data every 30 minutes
curl -X POST https://{EKODB_API_URL}/api/schedules \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Warm Cache",
"function_label": "warm_product_cache",
"cron_expression": "0 */30 * * * *",
"parameters": {"top_n": 100},
"enabled": true
}'

Backup & Archival

# Archive old data on the 1st of each month
curl -X POST https://{EKODB_API_URL}/api/schedules \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Monthly Archive",
"function_label": "archive_old_data",
"cron_expression": "0 0 0 1 * *",
"parameters": {
"archive_collection": "archived_data",
"months_old": 6
},
"enabled": true
}'

Parameters

A schedule passes a fixed parameters object to the function on every run. The values are stored verbatim and bound to the function's declared parameters when it executes.

curl -X POST https://{EKODB_API_URL}/api/schedules \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Daily Report",
"function_label": "generate_report",
"cron_expression": "0 0 1 * * *",
"parameters": {
"start_date": "2026-01-01T00:00:00Z",
"recipient": "reports@company.com"
},
"enabled": true
}'
Generating "now" inside the function

There is no {{NOW}} / {{NOW-1d}} schedule-parameter substitution. The function parameter substituter only resolves call-time {{param}} placeholders and {{env.VAR}} environment values. To stamp the current time inside a function, use the CurrentDatetime stage (writes UTC "now" to a working-record field) rather than a magic schedule parameter.

Best Practices

1. Choose Appropriate Times

# ✅ Good - Run during low-traffic hours
"cron_expression": "0 0 3 * * *" # 3 AM

# ❌ Avoid - Peak business hours
"cron_expression": "0 0 12 * * *" # Noon

2. Set Realistic Intervals

# ✅ Good - Hourly for data sync
"cron_expression": "0 0 * * * *"

# ❌ Avoid - Every second (too frequent)
"cron_expression": "* * * * * *"

3. Monitor Execution Times

Retrieve a schedule and check stats.avg_execution_time_ms. If execution time is growing, consider optimizing the underlying function.

curl -X GET https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}"

# Check stats.avg_execution_time_ms in the response

4. Handle Failures Gracefully

Build error handling into your function definition with the TryCatch stage — it runs try_functions, and if any fail, runs catch_functions instead (capturing the error into output_error_field):

curl -X POST https://{EKODB_API_URL}/api/functions \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"label": "safe_cleanup",
"functions": [
{
"type": "TryCatch",
"try_functions": [
{
"type": "Delete",
"collection": "logs",
"filter": {
"type": "Condition",
"content": {
"field": "created_at",
"operator": "Lt",
"value": "{{cutoff_date}}"
}
}
}
],
"catch_functions": [
{
"type": "Insert",
"collection": "cleanup_errors",
"record": { "stage": "delete_logs", "error": "{{error}}" }
}
],
"output_error_field": "error"
}
]
}'

5. Set the Timezone Instead of Pre-Converting to UTC

Cron expressions are evaluated in the schedule's timezone, so write the expression in local wall-clock time and let the scheduler handle the UTC conversion (including daylight saving). Set timezone to the IANA name for the location.

# A 9 AM Eastern weekday report — no manual UTC math, DST handled for you
curl -X POST https://{EKODB_API_URL}/api/schedules \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Business Hours Report",
"function_label": "report",
"cron_expression": "0 0 9 * * MON-FRI",
"timezone": "America/New_York",
"enabled": true
}'

6. Test With a Wide Cron, Then Tighten

The manual trigger endpoint only fires schedules that are currently enabled — the scheduler caches enabled schedules in memory and trigger looks the schedule up there, so a disabled schedule returns Schedule not found. To dry-run a new schedule, create it enabled with a far-future or infrequent cron, trigger it manually to verify, then update the cron to the real cadence.

# 1. Create enabled with an infrequent cron so it won't auto-fire while testing
curl -X POST https://{EKODB_API_URL}/api/schedules \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "New Schedule",
"function_label": "new_function",
"cron_expression": "0 0 0 1 1 *",
"enabled": true
}'

# 2. Manually trigger to test (works only on enabled schedules)
curl -X POST https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID}/trigger \
-H "Authorization: Bearer {TOKEN}"

# 3. Update to the real cadence after verification
curl -X PUT https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{"cron_expression": "0 0 * * * *"}'

Troubleshooting

Schedule Not Running

Problem: Schedule exists but function never executes

Solutions:

  1. Check enabled is true
  2. Verify cron expression is valid
  3. Check next_execution timestamp
  4. Ensure function exists with correct label
curl -X GET https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}"

# Verify enabled, next_execution, and function_label in the response

Function Fails

Problem: Schedule runs but function execution fails

Solutions:

  1. Check stats.last_error for error message
  2. Verify function parameters are correct
  3. Test function manually
  4. Check function permissions
curl -X GET https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}"

# Check stats.last_error and stats.failed_executions in the response

Schedule Runs at the Wrong Time

Problem: Schedule fires earlier or later than expected

Cause: The schedule's timezone is not what you expect, so the cron expression resolves against the wrong zone. The default is UTC.

Solution: Set timezone to the IANA name for your location. The cron expression is then interpreted in that zone with daylight saving handled automatically — no manual UTC conversion needed.

# Run at 9am New York time, DST-aware
curl -X PUT https://{EKODB_API_URL}/api/schedules/{SCHEDULE_ID} \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{"cron_expression": "0 0 9 * * MON-FRI", "timezone": "America/New_York"}'

API Reference

Create Schedule

POST /api/schedules

Request body:

FieldTypeRequiredDescription
namestringYesDisplay name for the schedule
descriptionstringNoOptional description
function_labelstringYesLabel of the function to execute
cron_expressionstringYes6-field cron expression
parametersobjectNoParameters to pass to the function
enabledbooleanYesWhether the schedule is active
timezonestringNoIANA timezone the cron expression is evaluated in, DST-aware (e.g. America/New_York); invalid names are rejected (default: UTC)
Admin authentication

All /api/schedules endpoints require an admin token (they are behind the server's admin auth filter).

List Schedules

GET /api/schedules

Get Schedule

GET /api/schedules/{id}

Update Schedule

PUT /api/schedules/{id}

Request body: Any subset of the fields from Create Schedule.

Delete Schedule

DELETE /api/schedules/{id}

Trigger Schedule

POST /api/schedules/{id}/trigger

Manually triggers immediate execution of a schedule, regardless of cron timing. Works only on schedules that are currently enabled — the scheduler resolves the trigger against its in-memory set of enabled schedules, so a disabled schedule returns Schedule not found.

Schedule Stats

GET /api/schedules/stats

Returns aggregate statistics across all schedules.

Summary

Scheduled functions in ekoDB enable:

Automation - Recurring tasks without manual intervention ✅ Flexible scheduling - Cron-based with 6-field precision ✅ Background execution - Non-blocking operation ✅ Monitoring - Built-in execution statistics ✅ Error tracking - Automatic error logging ✅ Production-ready - Reliable and maintainable

Note: cron expressions are evaluated in the schedule's timezone (any IANA name, DST-aware; defaults to UTC).