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 syncState (with lag.percentDataIngested and lag.snapshotsBehind for detail), 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.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:
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.
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.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
pendingExact now defaults to true: the controller diffs the live data-file sets of the head and synced snapshots, giving exact filesPending / bytesPending. This replaced defaulting to the cheap summary-totals estimate, because netting cumulative summary totals cannot answer the pending question at all once the source has non-append commits (compaction, overwrite, delete) between the synced and head snapshots — it silently reports nothing (pendingState=UNAVAILABLE) rather than a wrong number.
The exact diff enumerates files and is capped at iceberg.snapshotProcessing.lagMaxDiffFiles files per snapshot enumeration (default 100,000) — a per-table key in the ExternalTableSyncTask config. It is also bounded by a controller-wide cap on concurrent exact diffs that StarTree manages; either cap degrades the response to the summary estimate and says so in lag.message, with pendingState=UNAVAILABLE (or ESTIMATED if the fallback succeeds) and pendingExact: false.
Pass ?pendingExact=false to opt out to the O(1) summary-totals estimate up front.
Integration notes
- Pending fields can be
nulleven whensyncStateis notCAUGHT_UP. A compaction, overwrite, or delete between snapshots can leave both the exact diff and the summary estimate unable to answer (pendingState=UNAVAILABLE), sofilesPending,bytesPending, andpercentDataIngestedmay come backnull. Fall back tosnapshotsBehind;lag.messageandlag.unavailableReasonexplain why. - Two different message fields. Top-level
lagMessagemeanslagitself isnull(lag does not apply to the table); withoutincludeLag=trueneither key appears at all.lag.messagemeanslagis present but an individual field is degraded. - Display strings vs numbers.
startTime,endTime,committedAt,controllerTime,syncedAt, andtotalSizeare display strings. Every timestamp string has a raw epoch-milliseconds companion (startTimeMs,endTimeMs,committedAtMs,controllerTimeMs,syncedAtMs), andtotalSizehas a raw companion too (totalBytes) — use those for arithmetic.filesPending,bytesPending,percentDataIngested,totalFiles,totalRecords, andsnapshotsBehindremain numbers.
Snapshot ingestion (cross-run progress)
Use this when a single snapshot is too large to ingest in one run, and you need to know how far the ingestion as a whole has got — not just the last batch.fileOnboardingRun is overwritten with RUNNING the moment the next run starts and reports IDLE between runs, and each run’s own counts (filesDiscovered, segmentsUploaded) cover only that run’s page of at most iceberg.snapshotProcessing.batchSize files. Neither one answers “how far through this snapshot are we” for a snapshot ingested over many runs. The snapshotIngestion object in the Run status response does: it accumulates runs, filesDiscovered, segmentsUploaded, and filesFailed across every run that has processed the snapshot the table is currently on, alongside the snapshot’s own totalFiles for context.
It also carries the outcome of the last run that actually finished — lastRunStatus, lastRunAt, lastRunError — plus consecutiveFailedRuns, since a fresh RUNNING record can otherwise hide a failure for the length of one cron tick. The failure streak is counted per run, not per snapshot, so a run that re-targets a newer upstream snapshot does not reset it.
snapshotIngestion is null when the table has neither an ingestion watermark nor a finished run yet (a brand-new table before its first sync).
Ingestion watermark
GET /tables/{tableNameWithType}/externalTable/checkpoint
Use this to answer “is the snapshot fully ingested, or only partly?”
A large snapshot is ingested over many runs, one page of files at a time. A run that reports COMPLETED therefore says nothing about whether the snapshot as a whole is finished — it means that page succeeded. The watermark is what tracks the snapshot itself: which one the table is on, and how far through it ingestion has got.
Requires executor=controller; legacy minion-executor tables are rejected with 400, because they never write a watermark and would otherwise be indistinguishable from a table that has not run yet. All value fields are null when the table has never synced. The Data Portal does not surface this endpoint — call it directly.
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:
Sync health — a per-table latch driven by run outcomes, so a dashboard or alert doesn’t have to poll the status API:
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.

