How to read your data
Overview
Your project's complete data history is available through the Data for Analytics integration.
New to this integration?See How to Set Up Data Export for Analytics to get started first.
Data is stored in Delta format (Parquet files) and refreshed daily — covering all changes up to 00:00 UTC, available for consumption by 04:00 UTC.
Access is provided via the open-source delta-sharing protocol, designed for efficiently sharing large-scale datasets. You can query your data using the REST API or the Python delta-sharing library.
To get started, you'll need two credentials from your Data for Analytics integration point:
- Secret key
- Data storage URL
You can find both in your Maestra project under Integrations → Integration Setup → Data for Analytics integration point.
Limitations
Keep these constraints in mind before you begin:
- You can only create one Data for Analytics integration per project.
- Data for the previous calendar day is available by 04:00 UTC (covers all changes up to 00:00 UTC).
- You can request a maximum of 10 data versions per call.
startingVersionandendingVersionare required parameters. - Data removed by the Automated data cleanup setting is included in analytics. Records deleted manually are flagged with
_isDeleted = TRUE. - Analytics data accuracy is 99% — at least 99% of your project's records are available for export.
Encrypted Data
Some tables contain encrypted fields. To decrypt them, you'll need your Encryption Key, available in your Data for Analytics integration settings.
Python decryption script:
import base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
# Uses the AESGCM implementation from the cryptography library
def decrypt(encrypted_data_b64: str, master_key_b64: str) -> str:
# Decode Base64 inputs
encrypted_data = base64.b64decode(encrypted_data_b64)
master_key = base64.b64decode(master_key_b64)
NONCE_SIZE = 12 # AES-GCM nonce size
key_nonce = encrypted_data[0:NONCE_SIZE] # Nonce for decrypting the data key
value_nonce = encrypted_data[NONCE_SIZE:NONCE_SIZE*2] # Nonce for decrypting the value
encrypted_data_key = encrypted_data[24:72] # Encrypted data key
encrypted_value = encrypted_data[72:] # Encrypted value
# Decrypt the data key using the master key (Encryption Key from integration settings)
aesgcm_master = AESGCM(master_key)
data_key = aesgcm_master.decrypt(key_nonce, encrypted_data_key, associated_data=None)
# Decrypt the value using the data key
aesgcm_data_key = AESGCM(data_key)
decrypted_value = aesgcm_data_key.decrypt(value_nonce, encrypted_value, associated_data=None)
return decrypted_value.decode("utf-8")REST API
Key Concepts
| Term | Description |
|---|---|
share | A logical grouping of schemas — essentially a database. Currently there is one: exports. |
schema | A logical group of tables. Available schemas: CDP, ProcessingOrders, Mailings, AbTests. |
table | A Delta Lake table or view within a schema. |
List Available Databases
GET {Service URL}/shares
Headers
Authorization: Bearer {token}
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
maxResults | Int32 | Optional | Maximum number of databases to return. If more exist, nextPageToken is included in the response. |
pageToken | String | Optional | Token for retrieving the next page of results. |
Response
{
"items": [
{
"name": "string",
"id": "string"
}
],
"nextPageToken": "string"
}| Field | Description |
|---|---|
items | Array of available databases (may be empty). |
nextPageToken | Token for the next page of results. |
List Available Schemas
GET {Service URL}/shares/{share}/schemas
Headers
Authorization: Bearer {token}
Path Parameters
| Parameter | Description |
|---|---|
{share} | Database name. |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
maxResults | Int32 | Optional | Maximum number of schemas to return. If more exist, nextPageToken is included in the response. |
pageToken | String | Optional | Token for retrieving the next page of results. |
Response
{
"items": [
{
"name": "string",
"share": "string"
}
],
"nextPageToken": "string"
}| Field | Description |
|---|---|
items | Array of available schemas (may be empty). |
name | Schema name. |
share | Name of the parent database. |
nextPageToken | Token for the next page of results. |
List Tables in a Schema
GET {Service URL}/shares/{share}/schemas/{schema}/tables
Headers
Authorization: Bearer {token}
Path Parameters
| Parameter | Description |
|---|---|
{share} | Database name. |
{schema} | Schema name. |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
maxResults | Int32 | Optional | Maximum number of tables to return. If more exist, nextPageToken is included in the response. |
pageToken | String | Optional | Token for retrieving the next page of results. |
Response
{
"items": [
{
"name": "string",
"schema": "string",
"share": "string",
"shareId": "string",
"id": "string"
}
],
"nextPageToken": "string"
}| Field | Description |
|---|---|
items | Array of available schemas (may be empty). |
name | Table name. |
schema | Name of the schema containing this table. |
share | Name of the database containing this schema. |
id | Table ID. |
nextPageToken | Token for the next page of results. |
List All Tables in a Database
GET {Service URL}/shares/{share}/all-tables
Headers
Authorization: Bearer {token}
Path Parameters
| Parameter | Description |
|---|---|
{share} | Database name. |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
maxResults | Int32 | Optional | Maximum number of tables to return. If more exist, nextPageToken is included in the response. |
pageToken | String | Optional | Token for retrieving the next page of results. |
Response
{
"items": [
{
"name": "string",
"schema": "string",
"share": "string",
"shareId": "string",
"id": "string"
}
],
"nextPageToken": "string"
}| Field | Description |
|---|---|
items | Array of available tables (may be empty). |
name | Table name. |
schema | Name of the schema containing this table. |
share | Name of the database containing this schema. |
id | Table ID. |
nextPageToken | Token for the next page of results. |
Get Files in a Table
GET {Service URL}/shares/{share}/schemas/{schema}/tables/{table}/changes
Headers
Authorization: Bearer {token}
Path Parameters
| Parameter | Description |
|---|---|
{share} | Database name. |
{schema} | Schema name. |
{table} | Table name. |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
startingVersion | Long | Required | The version to start reading data from. |
endingVersion | Long | Required | The version to stop reading data at. |
Response
The response body contains three newline-delimited JSON objects:
{
"protocol": {
"minReaderVersion": 1
}
}
{
"metaData": {
"id": "ec1809ba-0be9-4f3a-b004-8bd20712c3e8",
"format": {
"provider": "parquet"
},
"schemaString": "{\"type\":\"struct\",\"fields\":[{\"name\":\"internalId\",\"type\":\"string\",\"nullable\":false,\"metadata\":{}},{\"name\":\"id\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"name\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"systemName\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"description\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_tenant\",\"type\":\"string\",\"nullable\":false,\"metadata\":{}},{\"name\":\"_isDeleted\",\"type\":\"boolean\",\"nullable\":true,\"metadata\":{}},{\"name\":\"_rowversion_ts\",\"type\":\"timestamp\",\"nullable\":false,\"metadata\":{}}]}",
"configuration": {
"enableChangeDataFeed": "true"
},
"partitionColumns": [
"_tenant"
],
"version": 4
}
}
{
"add": {
"url": "https://data-mesh-exports-production.storage.yandexcloud.net/stable/exports/Balances/_tenant%3DTenant/part-00067-191b93f1-c8d7-4300-aef8-03a8d4608a3a.c000.snappy.parquet?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240819T074744Z&X-Amz-SignedHeaders=host&X-Amz-Expires=36000&X-Amz-Credential=YCAJEtjPkLj9KXZ69Fc2yqrz0%2F20240819%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=b52b400427e276ba2f07d0d3f08ccac967fc1cea852d1ba532f6f22f03a692b6",
"id": "9ae10c0ff8387ea00f7387b70a5d85ee",
"partitionValues": {
"_tenant": "Mindbox"
},
"size": 2281,
"expirationTimestamp": 1724089664079,
"version": 4,
"timestamp": 1723720346485,
"stats": "{\"numRecords\":3,\"minValues\":{\"internalId\":\"1\",\"id\":1,\"name\":\"MainAccount\",\"systemName\":\"MainAccount\",\"_rowversion_ts\":\"2024-08-15T07:55:36.790Z\"},\"maxValues\":{\"internalId\":\"2\",\"id\":2,\"name\":\"AdditionalAccount\",\"systemName\":\"AdditionalAccount\",\"_rowversion_ts\":\"2024-08-15T07:55:36.790Z\"},\"nullCount\":{\"internalId\":0,\"id\":0,\"name\":0,\"systemName\":0,\"description\":3,\"_isDeleted\":3,\"_rowversion_ts\":0}}"
}
}The response body contains three newline-delimited JSON objects:
- Protocol object — contains protocol information.
- Metadata object — contains table metadata:
| Field | Description |
|---|---|
schemaString | The table schema. |
version | Maximum data version available in the table. Increments by 1 each day; versions with no events contain no data. |
- File object — contains file access details:
| Field | Description |
|---|---|
url | Signed URL to a Parquet file containing your data. Valid for 10 minutes. |
size | File size in bytes. |
version | Data version in the file. |
timestamp | Time the URL was signed. |
expirationTimestamp | Time the signature expires. |
Python Connector
For full method specifications, see the delta-sharing documentation.
Reading an entire table at once is not supported.Because tables can be very large, the
load_as_pandasandload_as_sparkmethods are disabled. Always read data in versioned increments.
The connector authenticates using a Profile.json credentials file.
Profile.json
{
"shareCredentialsVersion": 1,
"endpoint": "{Service URL}",
"bearerToken": "{Secret key}"
}| Field | Description |
|---|---|
Data storage URL | Your Data storage URL, found in Maestra admin dashboard → Integration settings. |
SecretKey | Your Secret key, found (and resettable) in Maestra admin dashboard → Integration settings. |
shareCredentialsVersion | Currently 1. If the credentials file format changes, you will receive a corresponding error. |
Reading Data
To get a list of available tables, use the following code:
import delta_sharing
from delta_sharing.protocol import DeltaSharingProfile, CdfOptions
from delta_sharing.rest_client import DataSharingRestClient
share_file_path = {path to file} + "/Profile.json" # set the path to the file
client = delta_sharing.SharingClient(share_file_path) # create a client for reading data
print(client.list_all_tables()) # list all databases, schemas, and tables in the storageTo access your data, create a REST client that retrieves table changes:
import delta_sharing
from delta_sharing.protocol import DeltaSharingProfile, CdfOptions
from delta_sharing.rest_client import DataSharingRestClient
share_file_path = {путь к файлу} + "/Profile.json" # set the path to the file
profile = DeltaSharingProfile.read_from_file(share_file_path) # create the profile for your REST client
rest_client = DataSharingRestClient(profile) # create your REST client
table = delta_sharing.Table(share="Database Name", schema="Schema Name", name="Table Name") # create a table to read data from
# Note: specifying version is mandatory when reading data
res = rest_client.list_table_changes(
table,
cdfOptions=CdfOptions(starting_version=0, ending_version=10)
) # read table changes from version 0 to version 10The res variable contains an actions array with links to Parquet files of table changes. Load these files into a dataframe for further processing.
Spark Connector
You can also read your data using Spark.
Reading an entire table at once is not supported.Use the
readChangeFeedoption to read incremental changes.
import delta_sharing
from pyspark.sql import SparkSession
import pathlib
# Creating a SparkSession
spark = (
SparkSession.builder
.config(
"spark.jars.packages",
"org.apache.hadoop:hadoop-azure:3.3.1,"
"io.delta:delta-core_2.12:2.2.0,"
"io.delta:delta-sharing-spark_2.12:3.2.0",
)
.config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension")
.config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog")
.getOrCreate()
)
share_file_path = str(pathlib.Path().resolve()) + "/Profile.json" # authenticate with the service
table_url = share_file_path + "#exports.ProcessingOrders.BonusPointChanges" # set the path to the target table
# write data from versions 1 and 2 to the spark dataframe shared_df
shared_df = (
spark.read.format("deltaSharing")
.option("readChangeFeed", "true")
.option("startingVersion", 1)
.option("endingVersion", 2)
.load(table_url)
)For a full example including reading and saving data to a DWH, see this article.
Updated 5 months ago

