Controller API Examples | Apache Pinot Docs
For the complete documentation index, see llms.txt. This page is also available as Markdown.

Controller API Examples

Detailed curl examples for commonly used controller endpoints.

This page provides detailed curl request and response examples for commonly used controller endpoints. For a categorized overview of all Pinot APIs, see the main API Reference.

The complete and interactive list of every controller endpoint is available in the Swagger UI at http://<controller-host>:<port>/help. For a visual walkthrough of the Swagger UI, see Controller Admin API.

Query Validation

POST /validateMultiStageQuery

Compile one or more SQL statements with the multi-stage engine without executing them. Pinot returns one validation result per input query, in the same order that the queries were submitted.

Use sql for a single statement:

curl -X POST "http://localhost:9000/validateMultiStageQuery" \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"sql":"SELECT * FROM mytable"}'

Use sqls for batch validation:

curl -X POST "http://localhost:9000/validateMultiStageQuery" \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"sqls":["SELECT COUNT(*) FROM mytable","SELECT invalidColumn FROM mytable"]}'

Response

[
  {
    "compiledSuccessfully": true,
    "errorMessage": null,
    "errorCode": null,
    "sql": "SELECT * FROM mytable"
  }
]

Each response object contains:

  • compiledSuccessfully: whether Pinot compiled the query successfully

  • errorMessage: compiler error text on failure, otherwise null

  • errorCode: Pinot query error code on failure, otherwise null

  • sql: the input SQL string for that result

For static validation, you can also send tableConfigs and schemas so the controller compiles against the provided table metadata instead of the controller's ZooKeeper-backed table cache. logicalTableConfigs and ignoreCase are also accepted for this static-cache path. When populating tableConfigs and schemas, use the same JSON objects returned by GET /tables/{tableName} and GET /schemas/{schemaName}.

SQL DDL

POST /sql/ddl

Execute one controller-managed SQL DDL statement for table metadata:

  • CREATE TABLE

  • DROP TABLE

  • SHOW TABLES

  • SHOW CREATE TABLE

Use this endpoint when you want a SQL alternative to the JSON /schemas and /tables APIs. The controller compiles DDL into the same stored Schema and TableConfig model, so SHOW CREATE TABLE reflects the metadata Pinot persists. Send the DDL in a JSON request body:

Use a Database header or a SQL db.table qualifier to target a database. If both are present, they must agree.

Use dryRun=true to validate without persisting:

Use a regular request when you want Pinot to apply the DDL. This example scopes the request with the Database header:

SHOW CREATE response

CREATE dry-run response

The real response includes the fully resolved schema and table config objects. warnings appears when Pinot accepts the DDL but needs to report a non-fatal compile warning.

High-level response semantics:

  • 201 Created for a successful CREATE TABLE

  • 200 OK for DROP TABLE, SHOW TABLES, SHOW CREATE TABLE, dry runs, and idempotent IF EXISTS or IF NOT EXISTS cases

  • 400 Bad Request for parse errors, semantic validation errors, oversized SQL, type-incompatible defaults, or conflicting database references

  • 404 Not Found for missing tables or schemas

  • 409 Conflict for duplicate creates without IF NOT EXISTS, logical-table references that block a drop, or races with another writer

See SQL Table DDL for syntax details and end-to-end examples.

Query Workload Propagation

Use these controller endpoints to define a named query workload, inspect the per-instance budget view, and recompute propagated budgets after broker or server topology changes. For the operational model behind these APIs, see Workload-Based Query Resource Isolation.

POST /queryWorkloadConfigs

Create or update a workload definition. The controller stores the config in ZooKeeper and pushes instance-specific budgets to the relevant brokers and servers.

Response

GET /queryWorkloadConfigs/instance/{instanceName}

Return the computed per-instance budgets that a specific broker or server should load. Pinot uses this endpoint during broker and server startup to fetch workload budgets asynchronously after restart.

Response

Pinot returns 404 Not Found when no workload budgets currently map to the requested instance.

POST /queryWorkloadConfigs/refresh

Recompute and re-push budgets for existing workload configs after a table, tenant, or workload mapping changes.

Refresh by table:

Refresh by workload:

Refresh by tenant:

resourceType accepts workload, table, or tenant. The optional nodeType filter accepts brokerNode or serverNode.

Response

Cluster

GET /cluster/configs

List all the cluster configs. These are fetched from Zookeeper from the CONFIGS/CLUSTER/<clusterName> znode.

Request

Response

POST /cluster/configs

Post new configs to cluster. These will get stored in the same znode as above i.e. CONFIGS/CLUSTER/<clusterName>. These properties are appended to the existing properties if keys are new, else they will be updated if key already exists.

Request

Response

DELETE /cluster/configs

Delete a cluster config.

Request

Response

Groovy static analysis configs

Pinot stores Groovy static analysis settings as cluster configs. These configs are keyed by:

  • pinot.groovy.all.static.analyzer

  • pinot.groovy.ingestion.static.analyzer

  • pinot.groovy.query.static.analyzer

The ingestion-specific and query-specific configs override pinot.groovy.all.static.analyzer for their respective contexts. POST /cluster/configs/groovy/staticAnalyzerConfig rejects any other top-level config key.

Each config value is a JSON object with these fields:

  • allowedReceivers

  • allowedImports

  • allowedStaticImports

  • disallowedMethodNames

  • methodDefinitionAllowed

GET /cluster/configs/groovy/staticAnalyzerConfig

Return the currently configured Groovy static analyzer configs, keyed by cluster config name.

Request

Response

POST /cluster/configs/groovy/staticAnalyzerConfig

Update one or more Groovy static analyzer configs. The request body is a map keyed by one or more of the three supported config names listed above.

Request

Response

GET /cluster/configs/groovy/staticAnalyzerConfig/default

Return Pinot's built-in default Groovy static analyzer config. Use this endpoint to fetch a full sample payload before editing cluster overrides.

Request

Response

GET /cluster/info

Gets cluster related info, such as cluster name

Request

Response

Health

GET /health

Check controller health. Status are OK or WebApplicationException with ServiceUnavailable and message

Request

Response

Leader

GET /leader/tables

Gets the leader resource map, which shows the tables that are mapped to each leader.

Request

Response

GET /leader/tables/<tableName>

Gets the leaders for the specific table

Request

Response

Table

GET /debug/tables/<tableName>

Debug information for the table, which includes metadata and error status about segments, ingestion, servers and brokers of the table

Request

Response

Application Quotas

Application-level query quotas allow you to limit the queries per second (QPS) issued by different applications connecting to Pinot, regardless of which tables or databases they query. Applications are identified by the applicationName query option. For more details on how application quotas interact with table and database quotas, see Query Quotas.

GET /applicationQuotas

Get all application QPS quotas. Returns a map of application names to their configured QPS quota values. Returns an empty map if no application quotas have been configured.

Request

Response

GET /applicationQuotas/{appName}

Get the QPS quota for a specific application. If a quota has been explicitly set for the given application, that value is returned. Otherwise, the cluster-level default application quota (applicationMaxQueriesPerSecond) is returned. Returns null if neither is configured.

Request

Response

POST /applicationQuotas/{appName}

Create or update the QPS quota for a specific application. The maxQueriesPerSecond query parameter specifies the new quota value. To remove a previously configured quota for an application (falling back to the cluster default), omit the maxQueriesPerSecond parameter or leave it empty.

Request

Response

To remove an application-specific quota:

Segments

GET /segments/{tableName}/invalidPartitionMetadata

Return a map of segment name to raw partition metadata JSON for segments whose partition metadata is invalid.

Use this endpoint when you need to find segments whose stored partition metadata cannot be trusted for partition-based routing or validation.

Request

You can optionally scope the validation to a single partition column:

Behavior

  • Without partitionColumn, Pinot treats null partition metadata as valid and returns segments whose metadata is malformed or where any column maps to more than one partition.

  • With partitionColumn, Pinot validates only that column and treats null metadata, malformed metadata, missing column metadata, or multiple partitions for the column as invalid.

Response

POST /segments/{tableName}/reload

Submit an asynchronous reload job for every segment in a table. The controller accepts either a table name with a type suffix such as myTable_OFFLINE, or a raw table name such as myTable.

Use the optional query parameters to scope the request:

Parameter
Type
Description

type

string

Optional table type filter when the path uses a raw table name. Supported values are OFFLINE and REALTIME.

forceDownload

boolean

Re-download immutable segments from deep store before reloading. Defaults to false.

targetInstance

string

Send reload messages only to a specific server instance.

instanceToSegmentsMap

string

JSON map of server instance to segment list. When present, this overrides targetInstance and reloads only the listed segments on the listed servers.

When forceDownload=true and the path uses a raw table name without a type, Pinot restricts the reload to the OFFLINE table because forced deep-store download is only supported for immutable segments.

Request

If you use instanceToSegmentsMap, URL-encode the JSON map and send it as a query parameter.

Response

The status string is itself a JSON object keyed by table name. Each entry includes the submitted reloadJobId, the number of server reload messages sent, and whether Pinot persisted job metadata in ZooKeeper for later status checks.

POST /segments/{tableName}/{segmentName}/reload

Submit an asynchronous reload job for a single segment. If the table path omits the type suffix, Pinot derives the table type from the segment name.

Query parameters

Parameter
Type
Description

forceDownload

boolean

Re-download the segment from deep store before reloading. Defaults to false.

targetInstance

string

Reload the segment only on a specific server instance.

Request

Response

POST /segments/{tableNameWithType}/uploadFromServerToDeepstore

Queue one or more realtime segments for upload from an online server replica into deep store. This is useful when a segment is missing from deep store or when you need to force a re-upload. The endpoint only accepts realtime tables with a type suffix such as myTable_REALTIME.

Query parameter
Type
Meaning

segmentNames

string list

Segment names to upload. When omitted or empty, Pinot returns success without queueing any uploads.

forceMode

boolean

When false (default), upload only when Pinot decides the deep-store copy is missing. When true, re-upload even if deep store already has a copy.

Request

Response

If the table is not realtime, Pinot returns 400 Bad Request. If named segments are missing from ZooKeeper metadata, Pinot skips them and only queues the segments it can resolve. When no segments remain after resolution, Pinot returns a success response saying there are no segments to upload.

POST /segments/reingested

Finalize a reingested realtime segment upload. Pinot uses this endpoint during pauseless disaster recovery after a server has rebuilt a failed LLC segment and uploaded the segment tarball into the segment store.

This endpoint only accepts realtime-table uploads in metadata mode. Operators usually do not call it directly; the server's reingestion flow calls it after POST /reingestSegment/{segmentName} finishes rebuilding the segment.

Query parameter
Type
Meaning

tableName

string

Required raw table name. Pinot resolves the realtime table internally.

Required header
Value

UPLOAD_TYPE

METADATA

DOWNLOAD_URI

URI of the reingested segment tarball already copied to the segment store

COPY_SEGMENT_TO_DEEP_STORE

true

Request

The multipart body contains one segment metadata tarball. Pinot reads the segment metadata from that tarball, updates the download URI, and marks the realtime segment as complete.

Response

DELETE /deleteSegmentsFromSequenceNum/{tableNameWithType}

Delete a contiguous tail of LLC segments per partition for a pauseless realtime table. For each input segment, Pinot finds that segment's partition and deletes every segment in the same partition whose sequence number is greater than or equal to the oldest supplied segment for that partition.

Use this endpoint during manual pauseless recovery when a failed segment build or upload left later segments inconsistent and you need Pinot to recreate them from the stream.

Query parameter
Type
Meaning

segments

string list

Required LLC segment names. Pinot treats the oldest supplied segment in each partition as the deletion starting point.

dryRun

boolean

When true, Pinot returns the per-partition deletion plan without deleting anything. When false, Pinot performs the deletion.

force

boolean

When false, Pinot requires the table to be realtime, pauseless-enabled, and currently paused. When true, Pinot bypasses the pauseless-enabled and paused-state checks.

Request

Preview the deletion plan first:

Apply the deletion after you have paused ingestion and verified the target partitions:

Response

Pinot skips segment names that are no longer present in the ideal state. For operational safety, run the endpoint once with dryRun=true and only rerun with dryRun=false after you verify the per-partition segment list.

GET /segments/segmentReloadStatus/{jobId}

Fetch the current status for a previously submitted reload job.

Request

Response

The typed response tracks overall progress, estimated completion time, job metadata, and any per-segment failures Pinot has collected so far.

GET /segments/{tableNameWithType}/needReload

Ask every server hosting a typed table whether any of its segments need a reload. This endpoint requires a table name with type suffix such as myTable_OFFLINE or myTable_REALTIME.

Query parameters

Parameter
Type
Description

verbose

boolean

Include per-server reload decisions in the response. Defaults to false.

Request

Response

Without verbose=true, Pinot still returns the top-level needReload flag but leaves serverToSegmentsCheckReloadList empty.

PUT /tables/<tableName>

Update the config for an existing typed table.

Query parameters

Parameter
Type
Description

validationTypesToSkip

string

Comma-separated validation types to skip during table-config validation.

force

boolean

Defaults to false. When true, Pinot still applies backward-incompatible upsert or dedup config changes that it would otherwise reject. Use only for controlled migrations.

By default, Pinot returns 400 Bad Request for backward-incompatible changes on existing upsert or dedup tables. The protected fields include upsert comparison columns, hash function, mode, deleteRecordColumn, out-of-order settings, dedup hash function, dedup time column, and the table time column when Pinot is using it as the default comparison or dedup time column. The safest path for those changes is still to create a new table and reingest the data.

For existing PARTIAL upsert tables, Pinot now allows partialUpsertStrategies and defaultPartialUpsertStrategy to change without force=true. The new strategy is not retroactive: existing merged values stay as stored, and the new behavior takes effect only after each consuming server restarts and reloads the table config. During a rolling restart, replicas can temporarily diverge on rows ingested during the rollout window; if that happens, reset the affected consuming segments after the rollout.

Request

Response

PUT /tableConfigs/<tableName>

Update a combined TableConfigs payload for the raw table name. Pinot uses this endpoint when you want to update schema-linked offline and realtime table configs together.

Query parameters

Parameter
Type
Description

validationTypesToSkip

string

Comma-separated validation types to skip during validation.

reload

boolean

Defaults to false. When true, Pinot reloads the table if the schema update is backward compatible.

forceTableSchemaUpdate

boolean

Defaults to false. When true, Pinot forces both the schema update and the included table-config updates even when they would normally be rejected as backward incompatible. This is intended for exceptional cases and should be used with caution.

The same upsert and dedup compatibility checks described above apply to the realtime or offline configs inside the TableConfigs payload. Without forceTableSchemaUpdate=true, Pinot rejects those backward-incompatible changes with 400 Bad Request.

As with PUT /tables/{tableName}, Pinot allows partialUpsertStrategies and defaultPartialUpsertStrategy updates for existing PARTIAL upsert tables without forceTableSchemaUpdate=true, subject to the same restart and potential segment-reset caveats.

Request

Response

DELETE /tables/<tableName>

Deletes a table from the cluster. By default, deleted segments are moved to a Deleted Segments area and retained for a configurable period (controlled by controller.deleted.segments.retentionInDays, default 7 days) before being permanently removed. This allows recovery if the deletion was accidental.

You can override this behavior by passing the retention query parameter to specify a custom retention period for the deleted segments. Setting retention=0d deletes segments immediately, bypassing the default retention period entirely.

Query Parameters

Parameter
Type
Required
Description

type

string

No

Table type (OFFLINE or REALTIME). If not specified, both types are deleted if they exist.

retention

string

No

Retention period for deleted segments (e.g., 0d for immediate deletion, 1d for one day). Overrides the cluster default.

Request (default behavior)

Request (immediate deletion)

Response

For large tables, the default delete operation may time out because it copies segments to the deleted-segments area. Using retention=0d bypasses this copy step, which can help avoid timeouts.

DELETE /schemas/<schemaName>

Deletes a schema from the cluster. A schema can only be deleted if no tables are currently using it. If a table still references the schema, the delete request will fail.

To delete both a table and its schema in a single workflow, first delete the table using DELETE /tables/<tableName>, then delete the schema using this endpoint.

Request

Response

In the Pinot Data Explorer UI, you can delete both the table and its schema together by checking the Delete Schema option in the delete table dialog. The UI will delete the table first and then automatically delete the associated schema.

Table Config Validation

Enhanced in Pinot 1.4.0 with cluster-aware validations (see PR #16675)

POST /tableConfigs/validate

Validates a table configuration before you create or update a table. This endpoint now performs cluster-aware validations by default, catching errors like missing tenant tags or unavailable minion instances that previously only surfaced during table creation.

The endpoint checks:

  • Schema and table config consistency

  • Tenant assignment validity (do instances with the required tags exist?)

  • Minion instance availability (if task configs reference minion)

  • Active task conflicts

Request

Parameters

Parameter
Type
Description

validationTypesToSkip

query

Comma-separated list of validation types to skip (e.g., TENANT,MINION_INSTANCES)

The supported validation types that can be skipped are: TENANT, MINION_INSTANCES, ACTIVE_TASKS.

Response

On success, returns the validated config. On failure, returns an error message describing the validation issue.

POST /tableConfigs/tune

Validates a combined TableConfigs payload and returns the tuned version that Pinot would store on table create or update. Send the raw tableName, the schema, and at least one of offline or realtime. Pinot validates the payload first; if validation succeeds, it applies tuner configs and then enforces minimum replica and storage quota constraints on each included table config before returning the tuned TableConfigs.

Parameters

Parameter
Type
Description

validationTypesToSkip

query

Optional comma-separated validation types to skip during validation. Supported values are ALL, TASK, UPSERT, TENANT, MINION_INSTANCES, and ACTIVE_TASKS.

Request

You can omit either offline or realtime when tuning a single typed table config, but Pinot still requires schema and at least one table config in the request body.

Response

On success, Pinot returns the tuned TableConfigs payload together with unrecognizedProperties, which lists any JSON paths Pinot ignored while parsing.

Minion Task APIs

GET /tasks/<taskType>/taskcounts

Returns a map from parent task name to aggregated subtask counts for the given minion task type.

Query Parameters

Parameter
Type
Required
Description

state

string

No

Filter by one or more comma-separated Helix task states such as IN_PROGRESS, FAILED, or COMPLETED. The filter applies to the parent task state, not to the per-subtask counters returned in the response.

table

string

No

Filter to parent tasks that have at least one subtask for the specified table name with type, such as myTable_OFFLINE.

Request

Response

Each response value is a subtask-count summary for one parent task. The counters represent the number of subtasks in each result bucket, while state only controls which parent tasks are included in the map.

Logical Table Management

Added in Pinot 1.4.0

Logical tables provide a unified view over multiple physical tables (REALTIME and OFFLINE). A query against a logical table internally scans all of its underlying physical tables, similar to a SQL VIEW with UNION semantics. This is useful for scaling large tables, performing ALTER TABLE workflows like Kafka topic reconfiguration, and managing time-based data layouts.

GET /logicalTables

List all logical table names in the cluster.

Request

Response

GET /logicalTables/<tableName>

Get the configuration of a specific logical table. The response body is the logical table config JSON.

Request

Response

POST /logicalTables

Create a new logical table. The request body is a logical table config. The physical tables referenced must already exist, and all physical tables must share a compatible schema.

Request

Response

PUT /logicalTables/<tableName>

Update an existing logical table by sending the full logical table config, for example to add or remove physical tables.

Request

Response

DELETE /logicalTables/<tableName>

Delete a logical table. This does not delete the underlying physical tables.

Request

Response

Rebalance

Enhanced in Pinot 1.4.0 with dry-run summary mode, pre-checks, and disk utilization info

POST /tables/<tableName>/rebalance

Trigger a rebalance for a table. In 1.4.0, this API gained several new capabilities:

  • Dry-run summary mode: Pass dryRun=true to get a summary of what the rebalance would do without making any changes.

  • Pre-checks: Pass preChecks=true to run validation checks (replica group info, disk utilization) before executing the rebalance.

  • Disk utilization threshold override: Use diskUtilizationThresholdOverride to customize the threshold for the disk utilization pre-check.

  • Tenant info: The rebalance response now includes tenant information.

  • minimizeDataMovement: Pass minimizeDataMovement=true to reduce the amount of data moved during the rebalance.

Request

Ingestion

POST /tables/<tableName>/pauseConsumption

Pause real-time consumption for a table.

Query parameter
Type
Meaning

comment

string

Optional comment stored with the administrative pause state.

batchSize

integer

Maximum number of consuming segments Pinot commits at once while pausing. Defaults to queueing all consuming segments in one batch.

batchStatusCheckIntervalSec

integer

How often, in seconds, the controller checks whether the current pause batch has finished committing. Default: 5.

batchStatusCheckTimeoutSec

integer

How long, in seconds, the controller waits for a pause batch to finish before failing the request. Default: 180.

For realtime tables with many partitions, use the batch parameters to spread the commit work across smaller pause batches instead of asking every consuming segment to commit at once. Pinot returns 400 Bad Request if any batch parameter is non-positive.

Request

Example with batching:

POST /tables/<tableName>/resumeConsumption

Resume real-time consumption for a table.

Query parameter
Type
Meaning

comment

string

Optional comment stored with the administrative resume state.

consumeFrom

string

Optional resume offset policy. Use lastConsumed to continue from the offsets stored in Pinot metadata, smallest to restart from the earliest available offsets, or largest to restart from the latest available offsets. Default: lastConsumed.

Request

Example with an administrative comment and an explicit resume policy:

GET /tables/<tableName>/pauseStatus

Return the current pause state for a realtime table together with the currently consuming segments.

Request

Response fields

Field
Type
Meaning

pauseFlag

boolean

Whether Pinot currently considers the realtime table paused.

consumingSegments

array of strings

Segment names that are still in the consuming state when the status is fetched.

reasonCode

string

Pause reason code, such as ADMINISTRATIVE for operator-driven pauses or STORAGE_QUOTA_EXCEEDED for automatic storage-quota pauses.

comment

string

Stored administrative or automatic pause comment. If no explicit comment was stored, Pinot returns a default paused or unpaused message.

timestamp

string

Stored pause-state timestamp from the controller metadata.

indexOfInactiveTopics

array of integers

Zero-based topic indexes that remain paused after topic-level pause operations. Pinot omits this field when no individual topics are paused.

Example response

GET /tables/<tableName>/badLLCSegmentsPerPartition

Return the bad LLC segments for a realtime table, grouped by partition ID. Pinot sorts the segment names within each partition by increasing sequence number, which makes this endpoint useful before calling repair workflows such as deleteSegmentsFromSequenceNum.

Request

Response

If Pinot finds no bad LLC segments, it returns an empty JSON object.

Other Notable APIs (1.4.0)

The following APIs were added or enhanced in Pinot 1.4.0. Refer to Swagger for complete request/response details.

Endpoint
Method
Description

/tables/{tableName}/badLLCSegmentsPerPartition

GET

Returns bad LLC segments grouped by partition ID

/tables/{tableName}/removeIngestionMetrics

POST

Removes stale ingestion metrics for a table

/debug/serverRoutingStats

GET

Returns server routing stats as JSON (previously returned a string)

/tables/{tableName}/idealstate

GET

Now accepts optional segmentNames parameter to filter results

/tables/{tableName}/externalview

GET

Now accepts optional segmentNames parameter to filter results

/tenants/{tenantName}/tables

GET

Now supports withTableProperties parameter for richer tenant info

/query_range

GET/POST

Prometheus-compatible time series query endpoint (Beta)

For the complete and interactive list of all controller APIs, refer to the Swagger UI at http://<controller-host>:<port>/help. For a categorized overview of every endpoint documented on this site, see the main API Reference.

Last updated

Was this helpful?