Skip to main content
Once an External Table is created, the controller’s External Table watcher ingests data on a schedule. These read-only endpoints let you check each run’s status and see how far the queryable data lags behind the upstream table. There is also an endpoint to trigger a run immediately instead of waiting for the next scheduled tick.
The observability endpoints require executor=controller in the table’s ExternalTableSyncTask config (the default for tables onboarded via the Data Portal or the preview API).All paths are relative to your data-plane base URL — set export BASE_URL=https://dp.<data-plane-id>.cp.<region>.startree.cloud/api/pinot (the StarTree Cloud data-plane proxy). Add an Authorization header if your controller requires auth.
Run status is not the same as data freshness. A fileOnboardingRun.status of COMPLETED only means the last sync run finished — the table can still be behind the source, because the upstream table may have committed more data since. To judge whether your data is current, use the lag object (caughtUp, percentDataIngested, snapshotsBehind), not the run status.

Common tasks

A map from what you want to know to the call that answers it and the field to read. Each row links to the full reference below.
Prefer clicking to curl? A table’s details page in the Data Portal shows the same run status and sync controls — see Trigger a sync run.

API Endpoints Quick Reference


Run status

GET /tables/{tableNameWithType}/externalTable/status Use this to answer “did the last sync run work?” and — with includeLag=true — “is my data current?” Returns the External Table sync watcher’s last run status for the table, under a fileOnboardingRun object. With includeLag=true, the response additionally reports how far the table’s queryable data is behind the upstream Iceberg table — see Ingestion lag. Top-level fields: fileOnboardingRun fields:
Status codes:

Ingestion lag

GET /tables/{tableNameWithType}/externalTable/status?includeLag=true Use this when you need to know whether the table is current with the source — and, if not, by how much.
Ingestion lag reporting is available starting in StarTree release 0.16.0.
Reports how far the table’s queryable data is behind the upstream Iceberg table. Lag is expressed as a snapshot delta rather than a time lag: the last fully synced snapshot versus the upstream head snapshot, plus the files and bytes still pending. Lag applies to catalogType=iceberg-rest tables synced with executor=controller. For any other table the endpoint still returns 200 with lag: null and a lagMessage explaining why; the status response is never failed because of lag. Snapshot lag is undefined for raw S3 listings, for example.
lag object fields: Snapshot fields (synced and upstream):
There is deliberately no lagMs field. To express lag as a duration, subtract lag.synced.committedAtMs from lag.upstream.committedAtMs (raw epoch milliseconds; the ISO-8601 committedAt strings are seconds-precision display companions). Both timestamps come from the same upstream catalog clock, so the difference is not skewed by clock drift between the catalog and the Pinot controller.
Transient catalog or ZooKeeper failures never turn into a 500. The affected lag fields are returned as null with an explanatory message, and a synced state that exists but cannot be read is reported as unavailable rather than as a confident “nothing ingested”.

Exact pending mode

By default, filesPending and bytesPending are derived from the snapshot summary totals of the two snapshots: a single metadata read, cheap at any table size. Because it is a net delta, compaction rewriting files can make the numbers approximate. With pendingExact=true, the controller instead diffs the live data-file sets of the head and synced snapshots. The result is exact, but the diff enumerates files and is therefore capped at iceberg.snapshotProcessing.lagMaxDiffFiles files per snapshot enumeration (default 100,000) — a per-table key in the ExternalTableSyncTask config. Past the cap, the response degrades back to summary estimates and says so in lag.message, with pendingExact: false.

Integration notes

  • Pending fields can be null even when caughtUp=false. A compaction, overwrite, or delete between snapshots makes the cheap summary estimate unreliable, so filesPending, bytesPending, and percentDataIngested may come back null. Fall back to snapshotsBehind, or retry with pendingExact=true; lag.message explains.
  • Two different message fields. Top-level lagMessage means lag itself is null (lag does not apply to the table); without includeLag=true neither key appears at all. lag.message means lag is present but an individual field is degraded.
  • Display strings vs numbers. startTime, endTime, committedAt, controllerTime, and totalSize are display strings. Every timestamp string has a raw epoch-milliseconds companion (startTimeMs, endTimeMs, committedAtMs, controllerTimeMs) — use those for arithmetic. filesPending, bytesPending, percentDataIngested, totalFiles, totalRecords, and snapshotsBehind remain numbers.

Metrics and alerting

The status and lag endpoints answer on-demand questions. For dashboards and continuous monitoring, the controller also emits per-sync metrics (available wherever you scrape StarTree Pinot controller metrics). Per-phase sync timers (milliseconds) — how long each stage of a run takes; useful for latency dashboards and for finding where a slow sync spends its time: Deletion-vector convergence — only when deletion vectors are enabled; covers the server-readiness handshake:
There is no metric for ingestion lag or per-run success/failure today — monitor those through the status API:
  • Sync failed — poll …/externalTable/status and alert when fileOnboardingRun.status is FAILED (triage with failurePhase).
  • Data falling behind — poll …?includeLag=true and alert when lag.caughtUp is false, or lag.percentDataIngested stays below your threshold for longer than one sync interval.
  • Snapshot silently skipped — a run that reports COMPLETED with segmentsUploaded: 0 while lag.caughtUp is false. See Sync says COMPLETED but no data lands.

Trigger a sync run (optional)

POST /tasks/schedule?taskType=ExternalTableSyncTask&tableName=<tableNameWithType>
You normally do not need this. After a table is created, the controller’s watcher runs the first sync automatically and then re-syncs on the table’s schedule. Use this endpoint only to start a run immediately instead of waiting for the next tick.
A table’s details page in the Data Portal also exposes Schedule Now, Pause Sync, and Resume Sync for the sync task, so you don’t need to call these endpoints directly. Pausing removes the table’s sync schedule (already-ingested data is untouched); resuming restores it. See Task Observability for the broader task-monitoring view these controls sit alongside.