Owlmetry

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 TypeLabelScopeDefault ScheduleDescription
revenuecat_syncRevenueCat SyncProjectManualSyncs subscriber data from RevenueCat for all users in a project
revenuecat_user_backfillRevenueCat User BackfillProjectManualPages 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_cleanupData Retention CleanupSystemDaily 2am UTCDeletes events, metric events, and funnel events older than each project's retention policy
db_pruningDatabase PruningSystemHourlyDrops oldest event partitions when database exceeds size limit
soft_delete_cleanupSoft-Delete CleanupSystemDaily 3am UTCHard-deletes resources soft-deleted more than 7 days ago
partition_creationPartition CreationSystemDaily 4am UTCCreates monthly partitions for event tables
attachment_cleanupAttachment CleanupSystemDaily 5am UTCHard-deletes soft-deleted event attachments after a 7-day grace, sweeps orphaned uploads and files whose events were retention-pruned
issue_scanIssue ScanSystemHourlyClusters new error events into issues via fingerprinting; dispatches one issue.new notification per team summarizing the scan's new + regressed production issues
issue_notifyIssue NotifySystemHourly @ :05Sends per-project issue.digest notifications at each project's configured cadence; silent when nothing new or regressed
app_version_syncApp Version SyncSystemHourly @ :15Refreshes 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_syncApp Store Ratings SyncSystemDaily @ 04:30 UTCFans 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_syncApple Ads SyncProjectDaily @ 04:45 UTCResolves 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_syncApp Store Connect Reviews SyncSystemDaily @ 05:30 UTCFans 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:

  1. System alerts: Set SYSTEM_JOBS_ALERT_EMAIL in the server environment to receive an email on every system job completion or failure.
  2. Per-trigger alerts: Pass notify: true when 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

PermissionDescription
jobs:readView job runs for the team
jobs:writeTrigger and cancel jobs

Both permissions are included in the default agent key permissions. Users need admin role minimum to access jobs.

Ready to get started?

Connect your agent via MCP or CLI and start tracking.