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 IntegrationsIntegration SetupData 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. startingVersion and endingVersion are 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

TermDescription
shareA logical grouping of schemas — essentially a database. Currently there is one: exports.
schemaA logical group of tables. Available schemas: CDP, ProcessingOrders, Mailings, AbTests.
tableA Delta Lake table or view within a schema.

List Available Databases

GET {Service URL}/shares

Headers

Authorization: Bearer {token}

Query Parameters

ParameterTypeRequiredDescription
maxResultsInt32OptionalMaximum number of databases to return. If more exist, nextPageToken is included in the response.
pageTokenStringOptionalToken for retrieving the next page of results.

Response

{
  "items": [
    {
      "name": "string",
      "id": "string"
    }
  ],
  "nextPageToken": "string"
}
FieldDescription
itemsArray of available databases (may be empty).
nextPageTokenToken for the next page of results.

List Available Schemas

GET {Service URL}/shares/{share}/schemas

Headers

Authorization: Bearer {token}

Path Parameters

ParameterDescription
{share}Database name.

Query Parameters

ParameterTypeRequiredDescription
maxResultsInt32OptionalMaximum number of schemas to return. If more exist, nextPageToken is included in the response.
pageTokenStringOptionalToken for retrieving the next page of results.

Response

{
  "items": [
    {
      "name": "string",
      "share": "string"
    }
  ],
  "nextPageToken": "string"
}
FieldDescription
itemsArray of available schemas (may be empty).
nameSchema name.
shareName of the parent database.
nextPageTokenToken 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

ParameterDescription
{share}Database name.
{schema}Schema name.

Query Parameters

ParameterTypeRequiredDescription
maxResultsInt32OptionalMaximum number of tables to return. If more exist, nextPageToken is included in the response.
pageTokenStringOptionalToken for retrieving the next page of results.

Response

{
  "items": [
    {
      "name": "string",
      "schema": "string",
      "share": "string",
      "shareId": "string",
      "id": "string"
    }
  ],
  "nextPageToken": "string"
}
FieldDescription
itemsArray of available schemas (may be empty).
nameTable name.
schemaName of the schema containing this table.
shareName of the database containing this schema.
idTable ID.
nextPageTokenToken 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

ParameterDescription
{share}Database name.

Query Parameters

ParameterTypeRequiredDescription
maxResultsInt32OptionalMaximum number of tables to return. If more exist, nextPageToken is included in the response.
pageTokenStringOptionalToken for retrieving the next page of results.

Response

{
  "items": [
    {
      "name": "string",
      "schema": "string",
      "share": "string",
      "shareId": "string",
      "id": "string"
    }
  ],
  "nextPageToken": "string"
}
FieldDescription
itemsArray of available tables (may be empty).
nameTable name.
schemaName of the schema containing this table.
shareName of the database containing this schema.
idTable ID.
nextPageTokenToken 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

ParameterDescription
{share}Database name.
{schema}Schema name.
{table}Table name.

Query Parameters

ParameterTypeRequiredDescription
startingVersionLongRequiredThe version to start reading data from.
endingVersionLongRequiredThe 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:

  1. Protocol object — contains protocol information.
  2. Metadata object — contains table metadata:
FieldDescription
schemaStringThe table schema.
versionMaximum data version available in the table. Increments by 1 each day; versions with no events contain no data.
  1. File object — contains file access details:
FieldDescription
urlSigned URL to a Parquet file containing your data. Valid for 10 minutes.
sizeFile size in bytes.
versionData version in the file.
timestampTime the URL was signed.
expirationTimestampTime 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_pandas and load_as_spark methods 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}"
}
FieldDescription
Data storage URLYour Data storage URL, found in Maestra admin dashboard → Integration settings.
SecretKeyYour Secret key, found (and resettable) in Maestra admin dashboard → Integration settings.
shareCredentialsVersionCurrently 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 storage

To 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 10

The 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 readChangeFeed option 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.