Vena Table Export Endpoint

The Vena Table snapshot export APIs (POST /exports, GET /exports/{exportId}/status, GET /exports/{exportId}/data) are in beta. Contract and behavior may change before GA.

In this article, we explain how the Vena Table export endpoints create an asynchronous snapshot of a Vena Table, how to filter that snapshot with whereClause, and how size limits apply. The goal of these endpoints is to provide table data as a durable, paginated export object that downstream systems can consume safely — without streaming an unbounded live query in a single HTTP response.

Through combining create, status, and data calls, you can:

  1. Submit a scoped export job
  2. Wait until the snapshot is ready
  3. Page through rows in JSON or CSV

Overview

Unlike the Hierarchy and Intersections endpoints that return one page of live model data per request, Vena Table export is asynchronous:

StepEndpointPurpose
  1. Create
POST /api/public/v1/exportsEnqueue a snapshot build; returns a flat resource with id immediately
  1. Poll
GET /api/public/v1/exports/{exportId}/statusWait until "COMPLETED" (JSON string)
  1. Fetch
GET /api/public/v1/exports/{exportId}/dataRead pages with offset / limit (JSON or CSV)

Snapshots include expiresTime (createdTime + 24 hours). After expiry, status and data requests return 404 EXPORT_EXPIRED; create a new export.

Authentication: Basic Auth (API user + key), same as other Public Export API routes.
License: The Public Export API feature must be enabled for your organization. If it is not, these endpoints return 422 text/plain (The Public Export API feature is not enabled for this customer.), not 403 JSON.

Base URL:

https://{hub}.vena.io/api/public/v1

Replace {hub} with your region (for example us1, us2, eu1, ca3).


Size limits of an export snapshot

These limits apply to one export job (the whole snapshot), not to a single HTTP page:

LimitDefaultWhere enforcedWhat happens if exceeded
Total rows per snapshot20,000,000At create time (pre-flight COUNT)400 EXPORT_ROW_LIMIT_EXCEEDED — no job is enqueued. Narrow whereClause / columnNames and retry.
Zero-row resultallowedCreateJob is still enqueued. After COMPLETED, /data returns empty data, recordsReturned=0, nextPage=null.
Internal part size50,000 rows / gzip partBuild time (server config)Transparent to API clients; used for storage pagination
Page limit (standard)max 50,000Data request400 INVALID_PARAMETER if above max
Page limit (paid Public Export API)max 100,000Data request400 INVALID_PARAMETER if above max
Default page limit50,000When limit omittedFirst page returns up to 50,000 rows
Snapshot TTL24 hoursRead path404 EXPORT_EXPIRED after expiresTime

Important: The 20,000,000-row cap is the combined row count across all snapshot parts (totalRecords). Use whereClause to keep result sets under this limit. An empty string "" exports all permitted rows and is only recommended for small tables.

A build-time safety net aborts if streamed rows exceed the cap after create (status becomes "ERROR"; /data returns 400 EXPORT_UNAVAILABLE). If the job is already "COMPLETED" but a snapshot part cannot be read, /data returns 500 text/plain (generic server error), not EXPORT_UNAVAILABLE. Prefer relying on the create-time check and a tight whereClause.


Using whereClause

whereClause is a required body field on create. It is a VenaQL WHERE expression that scopes which table rows are included in the snapshot.

ValueBehavior
Missing / null400 INVALID_PARAMETER on whereClause
"" (empty string)Export all rows the caller may access (small tables only)
Non-empty stringMust be valid VenaQL syntax; validated synchronously before the job is enqueued

Syntax length is not validated in v1 — keep expressions practical and readable.

Why use whereClause?

Vena Tables can be large and without a filter you may hit the 20,000,000-row snapshot cap or export more data than your downstream system needs. UsewhereClauseto:

  • Stay under the row cap
  • Export only the period, region, or entity you care about
  • Reduce build time and storage

Example create request

{
  "exportType": "VENA_TABLE",
  "modelId": "123456789012345678",
  "tableId": "987654321098765432",
  "whereClause": "Region = 'North America' AND Year = '2024'",
  "columnNames": ["Region", "Year", "Revenue", "Cost"]
}

exportType must be the exact token VENA_TABLE (case-sensitive, no surrounding whitespace). Blank, unknown, lowercase (vena_table), or padded (VENA_TABLE) values return 400 INVALID_PARAMETER.

To export all columns on the table, omit columnNames. Unknown JSON properties on the body are ignored.

Common whereClause patterns

Equality

Region = 'East'

AND / OR

Region = 'East' AND Year = '2024'
Year = '2024' OR Year = '2025'

IN list

Year IN ('2023', '2024', '2025')

NULL checks — use IS NULL / IS NOT NULL

NULL is not a value in SQL. Comparisons with = or != against NULL evaluate to UNKNOWN, which filters out all rows. Syntax validation can still pass, but the query returns 0 rows. The export is created successfully, but the snapshot is empty

AvoidUse instead
Field = nullField IS NULL
Field != nullField IS NOT NULL
Field NOT IN ('a', null)Field NOT IN ('a', 'b') AND Field IS NOT NULL
Field BETWEEN null AND 100Field <= 100 (or an explicit non-null lower bound)
Field1 = 'x' AND Field2 = nullField1 = 'x' AND Field2 IS NULL

Always quote string literals

Unquoted words are treated as column references, not strings:

AvoidUse instead
Field = yesField = 'yes'
Field = 123Field = '123' (Vena Table cells are stored as strings)

LIKE wildcards

% matches any sequence; _ matches any single character. Escape literals when needed:

Field LIKE '10\_000' ESCAPE '\'

Invalid whereClause

Invalid VenaQL returns 400 INVALID_PARAMETER with param.path = ["whereClause"] before any job is created. Fix the expression and create again.


Endpoint 1: Create export (Beta)

POST /api/public/v1/exports

Creates an asynchronous snapshot. The response returns immediately with status SUBMITTED.

Success response

{
  "id": "111222333444555666",
  "status": "SUBMITTED",
  "exportType": "VENA_TABLE",
  "modelId": "123456789012345678",
  "tableId": "987654321098765432",
  "whereClause": "Region = 'East' AND Year = '2024'",
  "columnNames": ["Region", "Year", "Revenue"],
  "createdTime": "2026-08-05T12:00:00.000Z",
  "expiresTime": "2026-08-06T12:00:00.000Z"
}

Save id for status and data path {exportId}. expiresTime is createdTime + 24 hours. Fields are rebuilt from the persisted job/step (not a nested request echo). errorReason is omitted on create because status is always SUBMITTED.

Example

curl -s -X POST "https://{hub}.vena.io/api/public/v1/exports" \
  -u "${API_USER}:${API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "exportType": "VENA_TABLE",
    "modelId": "123456789012345678",
    "tableId": "987654321098765432",
    "whereClause": "Region = '\''East'\'' AND Year = '\''2024'\''",
    "columnNames": ["Region", "Year", "Revenue"]
  }'

Common errors on create

SituationHTTPerror.type
Missing / invalid whereClause400INVALID_PARAMETER
Invalid exportType (not exact VENA_TABLE)400INVALID_PARAMETER
Unknown column in columnNames400INVALID_PARAMETER
Model not found400INVALID_PARAMETER (param.path = modelId)
Table not found / not on model400INVALID_PARAMETER (param.path = tableId)
Pre-flight row count > 20,000,000400EXPORT_ROW_LIMIT_EXCEEDED
Missing model/table read permission403FORBIDDEN
Public Export API not licensed422text/plain (no JSON error.type)

Zero matching rows is not an error — the export is created and completes with an empty snapshot.


Endpoint 2: Get export status (Beta)

GET /api/public/v1/exports/{exportId}/status

Poll until the snapshot is ready. Typical interval: 2–5 seconds. Do not call /data until status is "COMPLETED".

The response is a JSON string (not an object). Internal ETL NOT_STARTED is never exposed — it is returned as "SUBMITTED".

Status values

StatusMeaning
SUBMITTEDJob received; waiting to enter the queue
QUEUEDIn the processing queue
RUNNINGSnapshot is being built
COMPLETEDReady — call /data
ERRORBuild failed — do not fetch data; submit a new export
EXPIREDTTL elapsed — create a new export

Example responses

"RUNNING"
"COMPLETED"
"ERROR"

Example

curl -s "https://{hub}.vena.io/api/public/v1/exports/${EXPORT_ID}/status" \
  -u "${API_USER}:${API_KEY}"

Common errors on status

SituationHTTPerror.type
Unknown {exportId}404NOT_FOUND
Invalid {exportId} format404NOT_FOUND
Past TTL404EXPORT_EXPIRED
Missing model permission403FORBIDDEN
Public Export API not licensed422text/plain

Endpoint 3: Get export data (Beta)

GET /api/public/v1/exports/{exportId}/data

Returns one page of snapshot rows. Prerequisite: status is "COMPLETED". Only the user who created the export may download data.

Query parameters

ParameterDefaultNotes
offset0Zero-based absolute row index
limit50000Max 50,000 (standard) or 100,000 (paid)
Acceptapplication/jsonOr text/csv for CSV page only

JSON response (default)

{
  "data": [
    ["North America", "2024", "1500000"],
    ["EMEA", "2024", "2300000"]
  ],
  "metadata": {
    "headers": ["Region", "Year", "Revenue"],
    "recordsReturned": 2,
    "nextPage": "https://us1.vena.io/api/public/v1/exports/111222333444555666/data?offset=2&limit=2",
    "totalRecords": 12000
  }
}

Each data item is a positional string array aligned with metadata.headers. Page metadata includes headers, recordsReturned, nextPage, and totalRecords — not offset, nextOffset, or expiresTime (TTL is on create). Follow metadata.nextPage until it is null. The sum of recordsReturned across pages equals totalRecords.

CSV response

Set Accept: text/csv. The body is header + up to limit data rows for the requested offset. There is no JSON metadata in the body — use JSON when you need nextPage or totalRecords.

Pagination pattern

GET .../data?offset=0&limit=50000
GET .../data?offset=50000&limit=50000
...
Stop when metadata.nextPage is null

Example

# JSON (default)
curl -s "https://{hub}.vena.io/api/public/v1/exports/${EXPORT_ID}/data?offset=0&limit=50000" \
  -u "${API_USER}:${API_KEY}"

# CSV
curl -s "https://{hub}.vena.io/api/public/v1/exports/${EXPORT_ID}/data?offset=0&limit=50000" \
  -u "${API_USER}:${API_KEY}" \
  -H "Accept: text/csv"

Common errors on data

SituationHTTPerror.type
Still building400EXPORT_NOT_READY
Build failed (ERROR / CANCELLED)400EXPORT_UNAVAILABLE
Bad offset / limit400INVALID_PARAMETER
Different user than creator403FORBIDDEN
Missing model permission403FORBIDDEN
Unknown / invalid id404NOT_FOUND
Expired id404EXPORT_EXPIRED
Unsupported Accept406—
Public Export API not licensed422text/plain
Part download/parse after COMPLETED500text/plain (generic server error)

Recommended client flow

1. POST /exports  with whereClause that keeps rows ≤ 20,000,000
2. Read response.id (and exportType / modelId / tableId / whereClause / columnNames)
3. Poll GET /exports/{id}/status every 2–5s until the JSON string is "COMPLETED"
4. GET /exports/{id}/data?offset=0&limit=50000
5. Follow metadata.nextPage until null (align data rows with metadata.headers)
6. Finish before expiresTime (24h) or create a new export

Error envelope

All JSON errors use:

{
  "error": {
    "type": "NOT_FOUND",
    "title": "Not Found",
    "message": "The export 111222333444555666 was not found. Ensure the exportId is correct and try again."
  }
}

Validation errors may include error.param (type: BODY or QUERY, path: field name). License failures and generic 500s are returned astext/plain, rather than this envelope. Branch on error.type for JSON errors, instead of relying on HTTP status alone.


Did this page help you?