Background Jobs
Asynchronous background work with progress tracking, scheduling, and email notifications.
Background jobs are asynchronous tasks that run on the server without blocking API responses. They are used for long-running operations like syncing data from third-party services, database maintenance, and scheduled cleanup.
Job Types
Each job has a type that determines what it does and a scope that controls who can trigger it.
| Job Type | Label | Scope | Default Schedule | Description |
|---|---|---|---|---|
revenuecat_sync | RevenueCat Sync | Project | Manual | Syncs subscriber data from RevenueCat for all users in a project |
revenuecat_user_backfill | RevenueCat User Backfill | Project | Manual | Pages through every customer in the linked RevenueCat project (V2 list-customers API) and creates or updates the corresponding app_users row (subscription state, lifetime USD revenue, attribution). Idempotent — re-running is safe. Anonymous RevenueCat IDs ($RCAnonymousID:*) are skipped. Use after installing the Owl SDK in an existing app to retrofit historical RC customers so Advertising Insights can attribute their revenue. Can take hours on large projects (~3 RC API calls per customer at the 480 req/min Customer Information budget) |
retention_cleanup | Data Retention Cleanup | System | Daily 2am UTC | Deletes events, metric events, and funnel events older than each project's retention policy |
db_pruning | Database Pruning | System | Hourly | Drops oldest event partitions when database exceeds size limit |
soft_delete_cleanup | Soft-Delete Cleanup | System | Daily 3am UTC | Hard-deletes resources soft-deleted more than 7 days ago |
partition_creation | Partition Creation | System | Daily 4am UTC | Creates monthly partitions for event tables |
attachment_cleanup | Attachment Cleanup | System | Daily 5am UTC | Hard-deletes soft-deleted event attachments after a 7-day grace, sweeps orphaned uploads and files whose events were retention-pruned |
issue_scan | Issue Scan | System | Hourly | Clusters new error events into issues via fingerprinting; dispatches one issue.new notification per team summarizing the scan's new + regressed production issues |
issue_notify | Issue Notify | System | Hourly @ :05 | Sends per-project issue.digest notifications at each project's configured cadence; silent when nothing new or regressed |
app_version_sync | App Version Sync | System | Hourly @ :15 | Refreshes each app's latest_app_version and apple_app_store_id (iTunes Lookup for Apple, computed from production events otherwise). System-scoped — cannot be triggered via the API, CLI, or MCP. See Latest Version Detection |
app_store_ratings_sync | App Store Ratings Sync | System | Daily @ 04:30 UTC | Fans out across every Apple iTunes storefront (~247 ISO2 codes) for each Apple app with a bundle_id, snapshotting per-country average rating + total count to app_store_ratings, then refreshing the worldwide-cache columns on apps. Dispatches an app.rating_changed notification per app whose worldwide rating count increased since the previous run (first-sync is suppressed). Project-scoped manual triggers also available via POST /v1/projects/:id/ratings/sync, owlmetry ratings sync, or sync-app-ratings MCP. See Store Ratings & Reviews |
apple_ads_sync | Apple Ads Sync | Project | Daily @ 04:45 UTC | Resolves any unresolved Apple Search Ads IDs to human-readable names AND pulls campaign + ad-group spend / impressions / taps / installs from Apple's Reports API into ad_campaign_lifetime + ad_adgroup_lifetime (filtered by adamId against each project's apps' apple_app_store_id, so projects sharing an Apple Developer account see only their own campaigns). Lifetime totals are computed from a rolling 12-month window in 90-day chunks. Project-scoped: trigger from the dashboard, POST /v1/projects/:id/ads/sync, owlmetry ads sync, or the sync-ads MCP tool. The daily cron fires the same handler with no project_id, fanning out across every project with an active Apple Search Ads integration. See Advertising Insights |
app_store_connect_reviews_sync | App Store Connect Reviews Sync | System | Daily @ 05:30 UTC | Fans out across every project with an active App Store Connect integration and pulls App Store reviews via the App Store Connect customerReviews API for every Apple app that has an apple_app_store_id. Paginates all reviews on every run and upserts on (app_id, store, external_id) — reviewer-side fields stay frozen, but developer_response_* fields refresh from ASC every sync, so replies added/edited/deleted in ASC's web UI flow back into Owlmetry. Then runs a refresh pass over rows still marked PENDING_PUBLISH (direct GET on customerReviewResponses/{id}, since the customerReviews payload doesn't reliably surface the publish flip), promoting them to PUBLISHED once Apple has and clearing developer_response_* if Apple 404s the response (deleted externally); count surfaced as pending_responses_checked in the job result. Dispatches an app.review_new notification per app when at least one new review row was inserted and the app already had reviews on file (no first-sync flood). Single-project manual triggers also available via POST /v1/projects/:id/integrations/app-store-connect/sync, owlmetry integrations sync app-store-connect, or sync-integration MCP. See Store Ratings & Reviews |
Project-scoped jobs are triggered by team admins via the dashboard, CLI, or API. They operate on a specific project and are visible to all team members with jobs:read permission.
System jobs run on a cron schedule and handle infrastructure maintenance. They are not visible through the API or dashboard — the server owner monitors them via local CLI commands and email alerts.
Job Lifecycle
Every job run goes through a lifecycle tracked in the job_runs table:
pending → running → completed
→ failed
→ cancelled- Pending: Job created, waiting to start execution.
- Running: Handler is actively executing. Progress updates may be written.
- Completed: Handler returned successfully.
- Failed: Handler threw an error. The error message is stored on the run.
- Cancelled: Cooperative cancellation — the handler checked
isCancelled()and returned early.
Progress Tracking
Jobs can report progress during execution as a structured object:
{
"processed": 45,
"total": 200,
"message": "Synced 38 users, 7 skipped"
}Progress is visible in real-time in the dashboard (via polling) and in the CLI with the --wait flag.
Scheduling
System jobs use cron expressions for scheduling, powered by pg-boss — a Postgres-native job queue. Scheduled jobs are delivered automatically when their cron fires. If a job is still running when its next scheduled time arrives, that run is skipped.
Overlap Prevention
Only one instance of each job type (per project for project-scoped jobs) can be running or pending at a time. Attempting to trigger a duplicate returns HTTP 409 with the existing run's ID.
Email Notifications
Two notification channels:
- System alerts: Set
SYSTEM_JOBS_ALERT_EMAILin the server environment to receive an email on every system job completion or failure. - Per-trigger alerts: Pass
notify: truewhen triggering a job (or check "Notify me when done" in the dashboard). The server resolves your email from your auth context and sends an alert when the job finishes.
Stale Run Recovery
If the server restarts while a job is running, any pending or running rows are automatically marked as failed with the message "Server restarted during execution" on the next startup.
Permissions
| Permission | Description |
|---|---|
jobs:read | View job runs for the team |
jobs:write | Trigger and cancel jobs |
Both permissions are included in the default agent key permissions. Users need admin role minimum to access jobs.
Attachments
Upload files alongside error events so engineers can reproduce bugs from the original bytes. A limited resource — use sparingly.
Latest Version Detection
How Owlmetry knows which version of your app is the current release, and how that powers the green/amber version badges shown across the dashboard, CLI, and MCP.
