Lance REST API
The Lance REST API is the Lance REST catalog protocol, served by
the Lance REST service. Lance clients such as lance-spark, lance-ray, and
pylance use it to create, register, describe, alter, and drop Lance tables whose metadata
Gravitino holds.
Base URL and authentication
Every path in this reference is relative to https://{lance_host}/lance, where {lance_host} is the
host the service is published on; see HTTPS. Each call needs the same
credentials the Gravitino server accepts: a bearer token from the identity provider, or a local
user's name and password.
Example
The script walks the Lance REST API from end to end: it gets an access token, creates a
namespace hierarchy, creates a table, inspects it, changes its columns, and drops it. It needs
curl, jq, and Python with pyarrow, and assumes the Lance REST service is published on the
Ingress as described in Client access.
The service accepts the same credentials as the Gravitino server. The script requests a token from the identity provider with the client credentials grant; any client the Gravitino server trusts works. The catalog namespace carries the S3 credentials that the service and Lance clients use to reach the table data.
#!/usr/bin/env bash
set -euo pipefail
# Set inputs
LANCE_URI="https://{lance_host}/lance"
TOKEN_ENDPOINT="{token_endpoint}"
CLIENT_ID="{client_id}"
CLIENT_SECRET="{client_secret}"
CATALOG="lance_catalog"
SCHEMA="demo"
TABLE="articles"
TABLE_LOCATION="s3://{bucket}/lance/${SCHEMA}/${TABLE}"
NS="${CATALOG}%24${SCHEMA}" # catalog$schema, with $ URL-encoded
TBL="${NS}%24${TABLE}" # catalog$schema$table
# Get an access token from the identity provider
TOKEN=$(curl -sSf "$TOKEN_ENDPOINT" \
-d grant_type=client_credentials \
-d client_id="$CLIENT_ID" \
-d client_secret="$CLIENT_SECRET" | jq -r '.access_token')
AUTH="Authorization: Bearer ${TOKEN}"
# Create the catalog namespace, which is a lakehouse generic catalog
curl -sSf -X POST "${LANCE_URI}/v1/namespace/${CATALOG}/create" \
-H "$AUTH" -H 'Content-Type: application/json' \
-d '{
"mode": "exist_ok",
"properties": {
"lance.storage.aws_region": "{aws_region}",
"lance.storage.aws_access_key_id": "{aws_access_key_id}",
"lance.storage.aws_secret_access_key": "{aws_secret_access_key}"
}
}' | jq .
# Create the schema namespace
curl -sSf -X POST "${LANCE_URI}/v1/namespace/${NS}/create" \
-H "$AUTH" -H 'Content-Type: application/json' \
-d '{"mode": "exist_ok"}' | jq .
# List the namespaces in the catalog
curl -sSf "${LANCE_URI}/v1/namespace/${CATALOG}/list" -H "$AUTH" | jq .
# Write the table schema as an Arrow IPC stream
python3 -c '
import sys, pyarrow as pa
schema = pa.schema([("id", pa.int64()), ("title", pa.string()), ("body", pa.string())])
pa.ipc.new_stream(sys.stdout.buffer, schema).close()
' > schema.arrow
# Create the table from the schema
curl -sSf -X POST "${LANCE_URI}/v1/table/${TBL}/create" \
-H "$AUTH" \
-H 'Content-Type: application/vnd.apache.arrow.stream' \
-H "x-lance-table-location: ${TABLE_LOCATION}" \
--data-binary @schema.arrow | jq .
# List the tables in the schema
curl -sSf "${LANCE_URI}/v1/namespace/${NS}/table/list" -H "$AUTH" | jq .
# Describe the table: its location, schema, version, and the storage options a client uses
curl -sSf -X POST "${LANCE_URI}/v1/table/${TBL}/describe" \
-H "$AUTH" -H 'Content-Type: application/json' -d '{}' | jq .
# Rename a column
curl -sSf -X POST "${LANCE_URI}/v1/table/${TBL}/alter_columns" \
-H "$AUTH" -H 'Content-Type: application/json' \
-d '{"alterations": [{"path": "body", "rename": "content"}]}' | jq .
# Drop a column
curl -sSf -X POST "${LANCE_URI}/v1/table/${TBL}/drop_columns" \
-H "$AUTH" -H 'Content-Type: application/json' \
-d '{"columns": ["title"]}' | jq .
# Drop the table, deleting its metadata and data
curl -sSf -X POST "${LANCE_URI}/v1/table/${TBL}/drop" \
-H "$AUTH" -H 'Content-Type: application/json' -d '{}' | jq .
Supported operations
The service supports the namespace and table operations below. Index operations are not supported.
| Operation | Description | HTTP Method | Endpoint Pattern |
|---|---|---|---|
| CreateNamespace | Create a new Lance namespace | POST | /lance/v1/namespace/{id}/create |
| ListNamespaces | List all namespaces under a parent namespace | GET | /lance/v1/namespace/{parent}/list |
| DescribeNamespace | Retrieve detailed information about a specific namespace | POST | /lance/v1/namespace/{id}/describe |
| DropNamespace | Delete a namespace | POST | /lance/v1/namespace/{id}/drop |
| NamespaceExists | Check whether a namespace exists | POST | /lance/v1/namespace/{id}/exists |
| ListTables | List all tables in a namespace | GET | /lance/v1/namespace/{id}/table/list |
| CreateTable | Create a new table in a namespace | POST | /lance/v1/table/{id}/create |
| DescribeTable | Describe an existing Lance table | POST | /lance/v1/table/{id}/describe |
| DropTable | Delete a table including both metadata and data | POST | /lance/v1/table/{id}/drop |
| TableExists | Check whether a table exists | POST | /lance/v1/table/{id}/exists |
| RegisterTable | Register an existing Lance table to a namespace | POST | /lance/v1/table/{id}/register |
| DeregisterTable | Unregister a table from a namespace (metadata only, data remains) | POST | /lance/v1/table/{id}/deregister |
| DeclareTable | Declare a table and store the metadata without touching lance table data. | POST | /lance/v1/table/{id}/declare |
| AlterColumns | Rename columns of a table | POST | /lance/v1/table/{id}/alter_columns |
| DropColumns | Drop columns from a table | POST | /lance/v1/table/{id}/drop_columns |
For more details, refer to the Lance REST API specification.
Operation details
Mode values are case-insensitive; the examples use lowercase.
Namespace operations
CreateNamespace takes a mode: create fails if the namespace exists, exist_ok succeeds
either way, and overwrite replaces an existing namespace.
DropNamespace deletes everything under the namespace, child namespaces and tables, metadata and Lance data files alike. It cannot be undone.
Table operations
CreateTable writes a new Lance dataset from the schema in the request. RegisterTable records
a dataset that already exists, without moving it. DeclareTable records the table's metadata
without writing any data. The version in a CreateTable response is always null, meaning the
latest version.
DropTable deletes the metadata and the data files. DeregisterTable deletes only the metadata and leaves the data files in place.
Column operations
AlterColumns renames columns only. Each entry in alterations names a column in path and its
new name in rename; an entry that sets data_type, nullable, or virtual_column is rejected.
DropColumns takes the column names to drop in columns. Both return the table version after the
change.
Namespaces and identifiers
A Lance namespace has exactly two levels: a catalog, then a schema. Tables live only in a schema.
metalake
└── lance_catalog catalog namespace, a generic lakehouse catalog
└── sales schema namespace
└── orders table
- A catalog namespace is a generic lakehouse catalog. Create it through the service, as the example does, or through the Gravitino API.
- A schema namespace can be created only once its catalog exists.
- A namespace below the schema level, such as
lance_catalog/sales/archive, is rejected.
Identifiers join the levels with $, which must be URL-encoded as %24 in a request path:
Levels: lance_catalog, sales, orders
Identifier: lance_catalog$sales$orders
In a URL path: lance_catalog%24sales%24orders
Client examples
The examples create namespaces and tables from the shell, Java, and Python. They expect an access
token in the TOKEN environment variable, obtained as in the example.
- REST
- Java
- Python
# Create a catalog-level namespace
# mode: "create" | "exist_ok" | "overwrite" for create namespace/table; mode: "create" | "overwrite" for register table
curl -X POST https://{lance_host}/lance/v1/namespace/lance_catalog/create \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{
"id": ["lance_catalog"],
"mode": "create"
}'
# Create a schema namespace
# Note: %24 is URL-encoded '$' character used as delimiter
curl -X POST https://{lance_host}/lance/v1/namespace/lance_catalog%24schema/create \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{
"id": ["lance_catalog", "schema"],
"mode": "create"
}'
# Register an existing table
curl -X POST https://{lance_host}/lance/v1/table/lance_catalog%24schema%24table01/register \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{
"id": ["lance_catalog", "schema", "table01"],
"location": "s3://{bucket}/lance_catalog/schema/table01",
"mode": "create"
}'
# Declare a table
curl -X POST https://{lance_host}/lance/v1/table/lance_catalog%24schema%24table04/declare \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{
"id": ["lance_catalog", "schema", "table04"],
"location": "s3://{bucket}/lance_catalog/schema/table04"
}'
# Create a table with schema, the schema is inferred from the Arrow IPC file
curl -X POST \
"https://{lance_host}/lance/v1/table/lance_catalog%24schema%24table03/create" \
-H 'Content-Type: application/vnd.apache.arrow.stream' \
-H "Authorization: Bearer $TOKEN" \
-H "x-lance-table-location: s3://{bucket}/lance_catalog/schema/table03" \
-H "x-lance-table-properties: {}" \
--data-binary "@${ARROW_FILE}"
// Add dependency: implementation("org.lance:lance-namespace-core:0.4.5")
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.RootAllocator;
import org.lance.namespace.client.apache.ApiClient;
import org.lance.namespace.client.apache.api.TableApi;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
// Initialize allocator and namespace connection
private final BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);
Map<String, String> props = new HashMap<>();
props.put("uri", "https://{lance_host}/lance");
props.put("delimiter", "$");
props.put("header.Authorization", "Bearer " + System.getenv("TOKEN"));
LanceNamespace ns = LanceNamespace.connect("rest", props, allocator);
// Create catalog namespace
CreateNamespaceRequest createCatalogNsRequest = new CreateNamespaceRequest();
createCatalogNsRequest.addIdItem("lance_catalog");
createCatalogNsRequest.setMode("create");
ns.createNamespace(createCatalogNsRequest);
// Create schema namespace
CreateNamespaceRequest createSchemaNsRequest = new CreateNamespaceRequest();
createSchemaNsRequest.addIdItem("lance_catalog");
createSchemaNsRequest.addIdItem("schema");
createSchemaNsRequest.setMode("create");
ns.createNamespace(createSchemaNsRequest);
// Register a table
RegisterTableRequest registerTableRequest = new RegisterTableRequest();
registerTableRequest.setLocation("s3://{bucket}/lance_catalog/schema/table01");
registerTableRequest.setId(Arrays.asList("lance_catalog", "schema", "table01"));
registerTableRequest.setMode("create");
ns.registerTable(registerTableRequest);
// Declare a table
DeclareTableRequest declareTableRequest = new DeclareTableRequest();
declareTableRequest.setLocation("s3://{bucket}/lance_catalog/schema/table02");
declareTableRequest.setId(Arrays.asList("lance_catalog", "schema", "table02"));
ns.declareTable(declareTableRequest);
// Create a table with schema inferred from Arrow IPC file.
// For REST create API, location/properties are passed via headers.
org.apache.arrow.vector.types.pojo.Schema schema =
new org.apache.arrow.vector.types.pojo.Schema(
Arrays.asList(
Field.nullable("id", new ArrowType.Int(32, true)),
Field.nullable("value", new ArrowType.Utf8())));
byte[] body = ArrowUtils.generateIpcStream(schema);
TableApi tableApi = new TableApi(new ApiClient().setBasePath("https://{lance_host}/lance"));
Map<String, String> createTableHeaders = new HashMap<>();
createTableHeaders.put("x-lance-table-location", "s3://{bucket}/lance_catalog/schema/table03");
createTableHeaders.put("x-lance-table-properties", "{}");
createTableHeaders.put("Authorization", "Bearer " + System.getenv("TOKEN"));
tableApi.createTable(
"lance_catalog$schema$table03", body, "$", "create", createTableHeaders);
# Install: pip install lance-namespace==0.4.5
import os
import lance_namespace as ln
import requests
# Connect to Lance REST service
ns = ln.connect("rest", {
"uri": "https://{lance_host}/lance",
"header.Authorization": f"Bearer {os.environ['TOKEN']}",
})
# Create catalog namespace
create_catalog_ns_request = ln.CreateNamespaceRequest(id=["lance_catalog"])
catalog = ns.create_namespace(create_catalog_ns_request)
# Create schema namespace
create_schema_ns_request = ln.CreateNamespaceRequest(id=["lance_catalog", "schema"])
schema = ns.create_namespace(create_schema_ns_request)
# Register a table
register_table_request = ln.RegisterTableRequest(
id=['lance_catalog', 'schema', 'table01'],
location='s3://{bucket}/lance_catalog/schema/table01'
)
ns.register_table(register_table_request)
# Declare a table
declare_table_request = ln.DeclareTableRequest(
id=['lance_catalog', 'schema', 'table02'],
location='s3://{bucket}/lance_catalog/schema/table02'
)
ns.declare_table(declare_table_request)
# Create a table with schema inferred from Arrow IPC file.
# For REST create API, location/properties are passed via headers.
with open('schema.ipc', 'rb') as f:
body = f.read()
response = requests.post(
"https://{lance_host}/lance/v1/table/lance_catalog%24schema%24table03/create",
params={"delimiter": "$", "mode": "create"},
headers={
"Content-Type": "application/vnd.apache.arrow.stream",
"Authorization": f"Bearer {os.environ['TOKEN']}",
"x-lance-table-location": "s3://{bucket}/lance_catalog/schema/table03",
"x-lance-table-properties": "{}",
},
data=body,
timeout=30,
)
response.raise_for_status()
Related
- Lance REST service, for how the service runs and is configured
- Lance integration, for Spark, Ray, and other engines
- Lance tables