- Overview
- API Resources
Supply Chain & Retail Solutions API guide
This page covers the schema-management endpoints: saving a standard schema for an application, rolling it out for a tenant solution, upgrading to a new version, adding a new column, and inspecting what is currently rolled out. Use it together with Getting Started and the API Guide.
All schema endpoints are rooted at https://ingestion.peak.ai/api/v2/schema.
Lifecycle overview
A schema goes through three stages:
- Save the standard schema for an application (one-time per
appName+appVersion). - Roll out that standard schema for a specific tenant solution. This creates the warehouse tables (and a matching
<table_name>_failed_rowstable for each) and records a tenant-scoped schema entry. - Evolve the rolled-out schema over time — by upgrading to a new app version or by adding a new column to an existing table.
You can inspect what's been rolled out at any time using the list and get endpoints.
Save a standard schema
Saves an application-level standard schema that can later be rolled out to one or more tenant solutions. Each appName + appVersion combination must be unique.
POST https://ingestion.peak.ai/api/v2/schema
POST https://ingestion.peak.ai/api/v2/schema
Payload
| Field | Required | Description |
|---|---|---|
appName | Yes | Application identifier the schema belongs to (e.g., quote-pricing) |
appVersion | Yes | Semantic version of the schema (e.g., 1.0.0) |
schemaDefinition | Yes | Schema specification as a JSON string. See Schema definition structure below for what goes inside it. |
schemaSynergyTag | No | Optional tag used by downstream metric publishing |
dryRun | No | When true, validates the payload without persisting. Returns 200 OK instead of 201 Created. Default false. |
Schema definition structure
schemaDefinition is a JSON-encoded array of table definitions. Each table has columns, and each column declares its data type and a list of validations.
Per-table fields:
| Field | Required | Description |
|---|---|---|
tableName | Yes | Table name. Must match ^[A-Za-z_][A-Za-z0-9_]*$. |
columns | Yes | Non-empty array of column definitions (see below). |
primaryKeys | No | Array of column names that form the primary key. |
uniqueKeys | No | Array of arrays; each inner array is a set of column names that must be unique together. |
foreignKeys | No | Array of foreign-key definitions, each with referencedTable, columnMappings, and an optional constraintName. |
skipValidations | No | When true, runtime validation is skipped for this table. Default false. |
scheduleConfig | No | Per-table scheduled-ingestion configuration. See Scheduled ingestion. |
Per-column fields:
| Field | Required | Description |
|---|---|---|
columnName | Yes | Column name. Must match ^[A-Za-z_][A-Za-z0-9_]*$. |
dataType | Yes | One of boolean, date, float, integer, json, numeric, string, timestamp. See Data Types. |
validations | Yes | Non-empty array of validation rules (see below). |
precision | Conditional | Required when dataType is numeric (1–38). Rejected when dataType is float. Ignored for other types. |
scale | Conditional | Required when dataType is numeric (0–37). Rejected when dataType is float. Ignored for other types. |
defaultValue | No | Default value applied when the column is added to existing rows via schema upgrade or add-column. |
Validation rules — what to include in validations[]:
Every entry in a column's validations array is a { "type": "<rule>", ... } object. Two rules are always required at save-schema time:
- Every column must include
{ "type": "required" }. This declares the column's key as mandatory in every ingest payload — submitting a row without that key fails withDI_E_23N01. Omittingrequiredat save-schema returns400 Bad Request: "validations array must contain an object with type 'required' (all fields are mandatory)". - Every
dateortimestampcolumn must additionally include{ "type": "timestampFormat", "format": "<format>" }, whereformatis one of:YYYY-MM-DD— fordateYYYY-MM-DD HH:MI:SS— fortimestamp(no timezone)YYYY-MM-DD HH:MI:SS ±hh:mm— fortimestamp(with timezone)
All other validation types are optional — include them only where you want their effect. To turn one off, just leave it out of the validations array; there is no per-validator enabled: false flag.
| Type | Parameters | Effect when included |
|---|---|---|
nonNull | none | Value cannot be null or blank. (required controls key presence; nonNull controls the value.) |
minLength | value (integer) | Minimum string length. |
maxLength | value (integer) | Maximum string length. |
range | min, max (numbers) | Numeric value must fall within the inclusive range. |
enum | value (array) | Value must be one of the listed options. |
Can a column be "optional"?
The schema treats every defined column as mandatory — there is no { "type": "required", "value": false } opt-out. To allow rows to carry no data for a column, keep required but omit nonNull. Callers must still include the key in every payload, but they can send null for rows that have no value for that column.
This is why every per-table page in the API Guide shows Required: Yes for every column. Whether the value can be null is reported separately as Nullable: Yes/No on the same row — required controls the key, nonNull controls the value.
If you need a column that may be entirely missing from some payloads, add it after rollout through the add-column endpoint instead — that endpoint does not enforce the required-validator rule, and any existing rows are backfilled with the supplied defaultValue.
Example column definitions:
"columns": [
{ "columnName": "order_id", "dataType": "string",
"validations": [{ "type": "required" }, { "type": "nonNull" }] },
{ "columnName": "amount", "dataType": "numeric", "precision": 10, "scale": 2,
"validations": [{ "type": "required" }, { "type": "nonNull" }, { "type": "range", "min": 0, "max": 1000000 }] },
{ "columnName": "placed_at", "dataType": "timestamp",
"validations": [{ "type": "required" }, { "type": "nonNull" }, { "type": "timestampFormat", "format": "YYYY-MM-DD HH:MI:SS" }] },
{ "columnName": "notes", "dataType": "string",
"validations": [{ "type": "required" }] }
]
"columns": [
{ "columnName": "order_id", "dataType": "string",
"validations": [{ "type": "required" }, { "type": "nonNull" }] },
{ "columnName": "amount", "dataType": "numeric", "precision": 10, "scale": 2,
"validations": [{ "type": "required" }, { "type": "nonNull" }, { "type": "range", "min": 0, "max": 1000000 }] },
{ "columnName": "placed_at", "dataType": "timestamp",
"validations": [{ "type": "required" }, { "type": "nonNull" }, { "type": "timestampFormat", "format": "YYYY-MM-DD HH:MI:SS" }] },
{ "columnName": "notes", "dataType": "string",
"validations": [{ "type": "required" }] }
]
Here order_id, amount, and placed_at must always carry a non-null value; notes must carry the key but the value can be null.
Example request
curl -X POST \
'https://ingestion.peak.ai/api/v2/schema' \
-H 'Authorization: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"appName": "quote-pricing",
"appVersion": "1.0.0",
"schemaDefinition": "{ \"tables\": [ { \"name\": \"products\", \"columns\": [ ... ] } ] }",
"dryRun": false
}'
curl -X POST \
'https://ingestion.peak.ai/api/v2/schema' \
-H 'Authorization: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"appName": "quote-pricing",
"appVersion": "1.0.0",
"schemaDefinition": "{ \"tables\": [ { \"name\": \"products\", \"columns\": [ ... ] } ] }",
"dryRun": false
}'
Response (201 Created)
{
"id": "7be5d475-4494-4c73-a012-2c27c6da5f38",
"appName": "quote-pricing",
"appVersion": "1.0.0",
"schemaSynergyTag": null,
"createdAt": "2026-04-24T07:23:19.264Z",
"updatedAt": "2026-04-24T07:23:19.264Z",
"message": "Standard schema saved successfully"
}
{
"id": "7be5d475-4494-4c73-a012-2c27c6da5f38",
"appName": "quote-pricing",
"appVersion": "1.0.0",
"schemaSynergyTag": null,
"createdAt": "2026-04-24T07:23:19.264Z",
"updatedAt": "2026-04-24T07:23:19.264Z",
"message": "Standard schema saved successfully"
}
Status codes
201 Created— schema saved200 OK—dryRun: truevalidation succeeded400 Bad Request— invalidschemaDefinition409 Conflict— a schema with thisappName+appVersionalready exists500 Internal Server Error— unexpected failure
Roll out a schema to a tenant solution
Creates the warehouse tables for a tenant solution by referencing a previously saved standard schema. Each table is created alongside its <table_name>_failed_rows companion table — used by asynchronous validation to record failed rows.
POST https://ingestion.peak.ai/api/v2/schema/rollout
POST https://ingestion.peak.ai/api/v2/schema/rollout
Payload
| Field | Required | Description |
|---|---|---|
solutionName | Yes | Tenant-specific solution identifier (e.g., QP_OOTB) |
targetSchemaName | Yes | Warehouse schema where tables will be created (e.g., STAGE) |
appName | Yes | The standard schema's appName |
appVersion | Yes | The standard schema's appVersion |
prefix | No | String prepended to every table name during creation |
suffix | No | String appended to every table name during creation |
Example request
curl -X POST \
'https://ingestion.peak.ai/api/v2/schema/rollout' \
-H 'Authorization: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"solutionName": "QP_OOTB",
"targetSchemaName": "STAGE",
"appName": "quote-pricing",
"appVersion": "1.0.0",
"prefix": "QP_",
"suffix": "_OOTB"
}'
curl -X POST \
'https://ingestion.peak.ai/api/v2/schema/rollout' \
-H 'Authorization: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"solutionName": "QP_OOTB",
"targetSchemaName": "STAGE",
"appName": "quote-pricing",
"appVersion": "1.0.0",
"prefix": "QP_",
"suffix": "_OOTB"
}'
Response (201 Created)
{
"id": "f81c9af3-65ce-4dfe-9cd5-855ebb79e617",
"solutionName": "QP_OOTB",
"schemaVersion": 1,
"targetSchemaName": "STAGE",
"prefix": "QP_",
"suffix": "_OOTB",
"createdAt": 1745484199264,
"message": "Schema rolled out successfully",
"successfulObjects": {
"QP_PRODUCTS_OOTB": "Created",
"QP_CUSTOMERS_OOTB": "Created"
},
"failedObjects": {}
}
{
"id": "f81c9af3-65ce-4dfe-9cd5-855ebb79e617",
"solutionName": "QP_OOTB",
"schemaVersion": 1,
"targetSchemaName": "STAGE",
"prefix": "QP_",
"suffix": "_OOTB",
"createdAt": 1745484199264,
"message": "Schema rolled out successfully",
"successfulObjects": {
"QP_PRODUCTS_OOTB": "Created",
"QP_CUSTOMERS_OOTB": "Created"
},
"failedObjects": {}
}
Status codes
201 Created— every table was created207 Multi-Status— some tables succeeded, some failed; inspectsuccessfulObjectsandfailedObjects400 Bad Request— request validation failed (e.g., unknownappName/appVersion)500 Internal Server Error— every table failed, or unexpected failure
Audit columns added at rollout
Each table created by a rollout includes a fixed set of audit columns alongside the business columns from your schema. These are populated automatically by the API — you do not send them in your ingest payload, and they will not collide with your own column names.
Data table — every rolled-out table includes these 6 audit columns:
| Column | Description |
|---|---|
PEAKAUDITREQUESTID | Identifier of the request that ingested the row |
PEAKAUDITCREATEDAT | Timestamp when the row was first inserted |
PEAKAUDITUPDATEDAT | Timestamp when the row was last updated (changes on every UPSERT) |
PEAKAUDITREQUESTTIME | Timestamp the caller's request was received |
PEAKAUDITCREATEDBY | Platform user identifier from the x-auth-platformuserid header |
SYSPRIMARYKEY | Synthetic primary key used internally to deduplicate and join rows |
Failed-rows table (<table_name>_failed_rows) — the companion table created next to each data table includes the audit columns above (except PEAKAUDITUPDATEDAT, since failed rows are not updated), plus two extras specific to failure tracking:
| Column | Description |
|---|---|
LOADTYPE | "upsert" or "append", derived from the operationType of the request that produced the failed row |
ERRORCODES | JSON-encoded list of error codes the row failed against (see API Guide — Error codes) |
In practice, you don't need to query the failed-rows table directly — the Data Quality Dashboard surfaces the same data with filters and drill-downs. See Data Quality Dashboard.
Column-name casing differs by warehouse: Snowflake stores them uppercase and unquoted (PEAKAUDITREQUESTID); Redshift stores them quoted and camel-cased ("peakAuditRequestId"). They refer to the same logical column.
Upgrade a rolled-out schema to a new version
Computes the diff between two standard schema versions and applies only additive changes (new tables, new columns) to your already-rolled-out solution. Any removal — of a table, column, primary key, unique key, or foreign key — is rejected with 400 Bad Request.
POST https://ingestion.peak.ai/api/v2/schema/upgrade
POST https://ingestion.peak.ai/api/v2/schema/upgrade
Payload
| Field | Required | Description |
|---|---|---|
solutionName | Yes | The tenant solution to upgrade |
appName | Yes | Application identifier (must match the one used at rollout) |
previousAppVersion | Yes | The version currently rolled out |
newAppVersion | Yes | The version to upgrade to |
Example request
curl -X POST \
'https://ingestion.peak.ai/api/v2/schema/upgrade' \
-H 'Authorization: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"solutionName": "QP_OOTB",
"appName": "quote-pricing",
"previousAppVersion": "1.0.0",
"newAppVersion": "1.1.0"
}'
curl -X POST \
'https://ingestion.peak.ai/api/v2/schema/upgrade' \
-H 'Authorization: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"solutionName": "QP_OOTB",
"appName": "quote-pricing",
"previousAppVersion": "1.0.0",
"newAppVersion": "1.1.0"
}'
Response (200 OK)
{
"solutionName": "QP_OOTB",
"appName": "quote-pricing",
"previousAppVersion": "1.0.0",
"newAppVersion": "1.1.0",
"schemaVersion": 2,
"message": "Schema upgrade completed successfully",
"successfulObjects": {
"QP_PRODUCTS_OOTB": "Added column: discount_pct",
"QP_PROMOTIONS_OOTB": "Created"
},
"failedObjects": {},
"skippedObjects": {
"QP_CUSTOMERS_OOTB": "No changes detected"
}
}
{
"solutionName": "QP_OOTB",
"appName": "quote-pricing",
"previousAppVersion": "1.0.0",
"newAppVersion": "1.1.0",
"schemaVersion": 2,
"message": "Schema upgrade completed successfully",
"successfulObjects": {
"QP_PRODUCTS_OOTB": "Added column: discount_pct",
"QP_PROMOTIONS_OOTB": "Created"
},
"failedObjects": {},
"skippedObjects": {
"QP_CUSTOMERS_OOTB": "No changes detected"
}
}
Status codes
200 OK— every applicable change succeeded (or the diff was empty)207 Multi-Status— some changes succeeded, some failed400 Bad Request— diff includes a removal; the response'svalidationErrors[]lists the removals detected404 Not Found—solutionName,appName, or one of the versions does not exist500 Internal Server Error— every change failed, or unexpected failure
Add a new column
Adds a single column to an existing table without going through a full version upgrade. The new column is added with the supplied validations and default value.
POST https://ingestion.peak.ai/api/v2/schema/{objectName}/add-attribute
POST https://ingestion.peak.ai/api/v2/schema/{objectName}/add-attribute
Path parameters
objectName— The fully qualified warehouse table name as returned by Describe a solution's schema. For a standard rolled-out table that includes any solutionprefix/suffix(for exampleQP_PRODUCTS_OOTB); for a custom table it includes the automaticC_(Snowflake) orc_(Redshift) table prefix as well (for exampleC_sales_orders_v1). The lookup is case-insensitive.
Automatic column-name prefix on standard tables
When you add a column to a table created through a standard schema rollout, the API automatically prefixes the column name in the warehouse — C_ on Snowflake (uppercase), c_ on Redshift (lowercase) — to distinguish post-rollout additions from the original schema columns. The customer sends the raw name in columnName; the prefixed name is what gets created in the warehouse, and what the response returns.
You send columnName | Tenant warehouse | Warehouse column created | Response columnName |
|---|---|---|---|
discount_pct | Snowflake | C_discount_pct | "C_discount_pct" |
discount_pct | Redshift | c_discount_pct | "c_discount_pct" |
Queries against the warehouse table must reference the prefixed name (SELECT C_discount_pct FROM ...).
Exception: columns added to a custom table are not prefixed — they keep the raw name you sent. The intent: the table's own C_/c_ prefix already signals it's a custom table, so individual columns inside don't need an additional marker.
Payload
| Field | Required | Description |
|---|---|---|
solutionName | Yes | The tenant solution the table belongs to |
columnName | Yes | Raw column name. The warehouse column is created with a C_/c_ prefix on standard tables (see above); custom-table columns are not prefixed. |
dataType | Yes | One of the supported data types — boolean, date, float, integer, json, numeric, string, timestamp. |
format | Conditional | Format string for date/timestamp types. Use YYYY-MM-DD for date; YYYY-MM-DD HH:MI:SS or YYYY-MM-DD HH:MI:SS ±hh:mm for timestamp. |
precision | Conditional | Total digits (1–38). Required for numeric; rejected for float. |
scale | Conditional | Decimal digits (0–37). Required for numeric; rejected for float. |
defaultValue | No | Default value applied to existing rows after the column is added. |
validations | Yes | Non-empty array of validation rules. Each entry is { "type": "<rule>", ... } (for example {"type": "nonNull"}, {"type": "maxLength", "value": 100}). Unlike save-schema, add-column does not require a {"type": "required"} entry. |
Controlling required-ness and nullability on the new column
required and nonNull are independent validators, and add-column accepts any combination — pick the pair that matches how the column should behave:
| You want | Include required? | Include nonNull? |
|---|---|---|
| Key must be present, value cannot be null | Yes | Yes |
| Key must be present, value can be null | Yes | No |
| Key may be missing; if present, value cannot be null | No | Yes |
| Key may be missing; if present, value can be null | No | No |
The doc pages on the per-table guide reflect these two flags as the Required and Nullable columns of the attributes table — required controls the key, nonNull controls the value.
defaultValue + nonNull are mutually exclusive. If you supply a defaultValue, the API rejects a payload that also lists {"type": "nonNull"} in validations with 400 Bad Request: "Cannot use 'nonNull' validation with defaultValue. defaultValue ensures the field always has a value." — pick one of the two, not both.
Example request
curl -X POST \
'https://ingestion.peak.ai/api/v2/schema/QP_PRODUCTS_OOTB/add-attribute' \
-H 'Authorization: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"solutionName": "QP_OOTB",
"columnName": "discount_pct",
"dataType": "numeric",
"precision": 5,
"scale": 2,
"defaultValue": 0,
"validations": [
{ "type": "nonNull" },
{ "type": "range", "min": 0, "max": 100 }
]
}'
curl -X POST \
'https://ingestion.peak.ai/api/v2/schema/QP_PRODUCTS_OOTB/add-attribute' \
-H 'Authorization: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"solutionName": "QP_OOTB",
"columnName": "discount_pct",
"dataType": "numeric",
"precision": 5,
"scale": 2,
"defaultValue": 0,
"validations": [
{ "type": "nonNull" },
{ "type": "range", "min": 0, "max": 100 }
]
}'
Response (200 OK)
{
"requestId": "7be5d475-4494-4c73-a012-2c27c6da5f38",
"message": "Column added successfully",
"columnName": "C_discount_pct",
"tableName": "QP_PRODUCTS_OOTB",
"schemaVersion": 2
}
{
"requestId": "7be5d475-4494-4c73-a012-2c27c6da5f38",
"message": "Column added successfully",
"columnName": "C_discount_pct",
"tableName": "QP_PRODUCTS_OOTB",
"schemaVersion": 2
}
The columnName in the response is the prefixed warehouse column name (C_discount_pct on Snowflake, c_discount_pct on Redshift) — that's the name you'll use when querying the warehouse. For a custom table, this would be the raw name you sent.
Status codes
200 OK— column added400 Bad Request— invalid validations, unsupporteddataType, or column already exists500 Internal Server Error— unexpected failure
Create a custom table
Adds a custom table to an existing tenant solution. Use this when a tenant needs a table beyond what the application's standard schema provides — for example, a tenant-specific staging table or a derived table that isn't part of the canonical schema. The columns are defined inline in the request payload.
The solution must already exist — create it first via Roll out a schema to a tenant solution. The new table inherits the solution's existing targetSchemaName automatically; you do not specify it on the request. Calling this endpoint with a solutionName that does not exist returns 404 Not Found.
POST https://ingestion.peak.ai/api/v2/schema/custom-table
POST https://ingestion.peak.ai/api/v2/schema/custom-table
Automatic name prefixing: the created warehouse table name is prefixed to distinguish it from standard-rollout tables — C_ on Snowflake (uppercase), c_ on Redshift (lowercase). The prefix is added to the front of the final table name, including any partner-supplied prefix/suffix. So tableName: "orders" with prefix: "sales_" and suffix: "_v1" becomes C_sales_orders_v1 on Snowflake. Column names are not prefixed.
Payload:
| Field | Required | Description |
|---|---|---|
solutionName | Yes | The existing tenant solution to attach the new table to (e.g., QP_OOTB). Must already be rolled out. |
tableName | Yes | Base name for the new table. See the prefixing note above for how the final warehouse name is composed. |
columns | Yes | Non-empty list of column definitions. Each entry follows the same shape used in the standard schema (columnName, dataType, optional format, precision, scale, defaultValue, and a validations array). |
primaryKeys | No | Array of column names that form the primary key |
uniqueKeys | No | Array of arrays — each inner array is a set of column names that must be unique together |
prefix | No | Additional string prepended to the table name (inside the automatic C_/c_ prefix) |
suffix | No | String appended to the table name |
Example request:
curl -X POST \
'https://ingestion.peak.ai/api/v2/schema/custom-table' \
-H 'Authorization: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"solutionName": "QP_OOTB",
"tableName": "orders",
"prefix": "sales_",
"suffix": "_v1",
"columns": [
{ "columnName": "ORDER_ID", "dataType": "string", "validations": [{ "type": "required" }] },
{ "columnName": "AMOUNT", "dataType": "numeric", "precision": 10, "scale": 2, "validations": [{ "type": "required" }] },
{ "columnName": "PLACED_AT", "dataType": "timestamp", "validations": [{ "type": "required" }, { "type": "timestampFormat", "format": "YYYY-MM-DD HH:MI:SS" }] }
],
"primaryKeys": ["ORDER_ID"]
}'
curl -X POST \
'https://ingestion.peak.ai/api/v2/schema/custom-table' \
-H 'Authorization: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"solutionName": "QP_OOTB",
"tableName": "orders",
"prefix": "sales_",
"suffix": "_v1",
"columns": [
{ "columnName": "ORDER_ID", "dataType": "string", "validations": [{ "type": "required" }] },
{ "columnName": "AMOUNT", "dataType": "numeric", "precision": 10, "scale": 2, "validations": [{ "type": "required" }] },
{ "columnName": "PLACED_AT", "dataType": "timestamp", "validations": [{ "type": "required" }, { "type": "timestampFormat", "format": "YYYY-MM-DD HH:MI:SS" }] }
],
"primaryKeys": ["ORDER_ID"]
}'
Response (201 Created):
{
"message": "Custom table created successfully",
"tableName": "C_sales_orders_v1",
"solutionName": "QP_OOTB",
"schemaVersion": 3,
"columnCount": 3
}
{
"message": "Custom table created successfully",
"tableName": "C_sales_orders_v1",
"solutionName": "QP_OOTB",
"schemaVersion": 3,
"columnCount": 3
}
The warehouse table name returned in the response includes the automatic C_/c_ prefix. Ingest into the custom table by sending requests to POST /api/v2/objects/<returned-tableName> — the same endpoint as any other table.
Status codes:
201 Created— custom table created400 Bad Request— validation failure (missing required fields, invalid column definitions, schema-name issues)401 Unauthorized404 Not Found— referenced solution or schema does not exist409 Conflict— a table with this name already exists in the solution500 Internal Server Error— unexpected failure
List solutions for the tenant
Returns every solution that has been rolled out for the calling tenant, with the latest version's metadata. Useful for populating dropdowns and checking what's available.
GET https://ingestion.peak.ai/api/v2/schema/solutions
GET https://ingestion.peak.ai/api/v2/schema/solutions
Response (200 OK)
{
"solutions": [
{
"solutionName": "QP_OOTB",
"targetSchemaName": "STAGE",
"prefix": "QP_",
"suffix": "_OOTB",
"schemaVersion": 2,
"tableCount": 5,
"createdAt": "2026-04-24T07:23:19.264Z"
},
{
"solutionName": "atest_automation_5",
"targetSchemaName": "stage",
"prefix": null,
"suffix": null,
"schemaVersion": 1,
"tableCount": 3,
"createdAt": "2026-01-20T09:06:15.891Z"
}
]
}
{
"solutions": [
{
"solutionName": "QP_OOTB",
"targetSchemaName": "STAGE",
"prefix": "QP_",
"suffix": "_OOTB",
"schemaVersion": 2,
"tableCount": 5,
"createdAt": "2026-04-24T07:23:19.264Z"
},
{
"solutionName": "atest_automation_5",
"targetSchemaName": "stage",
"prefix": null,
"suffix": null,
"schemaVersion": 1,
"tableCount": 3,
"createdAt": "2026-01-20T09:06:15.891Z"
}
]
}
The solutions array is empty if no rollouts have happened yet for the tenant.
Describe a solution's schema
Returns the latest version's full schema for a given solution, including every table and its columns.
GET https://ingestion.peak.ai/api/v2/schema?solutionName={solutionName}
GET https://ingestion.peak.ai/api/v2/schema?solutionName={solutionName}
Query parameters
solutionName(required) — the solution to describe
Response (200 OK)
Each table in the schema array is keyed by objectName — the full warehouse table name as it exists in your warehouse, including any solution-level prefix/suffix from rollout. Tables created through Create a custom table appear here with their automatic C_ (Snowflake) or c_ (Redshift) prefix as well.
{
"solutionName": "QP_OOTB",
"targetSchemaName": "STAGE",
"appName": "quote-pricing",
"appVersion": "1.1.0",
"prefix": "QP_",
"suffix": "_OOTB",
"createdAt": "2026-04-24T07:23:19.264Z",
"schema": [
{
"objectName": "QP_PRODUCTS_OOTB",
"primaryKeys": ["product_id"],
"uniqueKeys": [],
"foreignKeys": [],
"columns": [
{ "columnName": "product_id", "dataType": "string", "validations": [{ "type": "required" }, { "type": "nonNull" }] },
{ "columnName": "product_name", "dataType": "string", "validations": [{ "type": "required" }, { "type": "maxLength", "value": 255 }] },
{ "columnName": "discount_pct", "dataType": "numeric", "precision": 5, "scale": 2, "validations": [{ "type": "required" }, { "type": "range", "min": 0, "max": 100 }] }
],
"isRolledOut": true,
"rolloutState": "SUCCESS",
"targetSchemaName": "STAGE"
},
{
"objectName": "C_sales_orders_v1",
"primaryKeys": ["order_id"],
"uniqueKeys": [],
"foreignKeys": [],
"columns": [
{ "columnName": "order_id", "dataType": "string", "validations": [{ "type": "required" }, { "type": "nonNull" }] },
{ "columnName": "amount", "dataType": "numeric", "precision": 10, "scale": 2, "validations": [{ "type": "required" }, { "type": "nonNull" }] },
{ "columnName": "placed_at", "dataType": "timestamp", "validations": [{ "type": "required" }, { "type": "nonNull" }, { "type": "timestampFormat", "format": "YYYY-MM-DD HH:MI:SS" }] }
],
"isRolledOut": true,
"rolloutState": "SUCCESS",
"targetSchemaName": "STAGE"
}
]
}
{
"solutionName": "QP_OOTB",
"targetSchemaName": "STAGE",
"appName": "quote-pricing",
"appVersion": "1.1.0",
"prefix": "QP_",
"suffix": "_OOTB",
"createdAt": "2026-04-24T07:23:19.264Z",
"schema": [
{
"objectName": "QP_PRODUCTS_OOTB",
"primaryKeys": ["product_id"],
"uniqueKeys": [],
"foreignKeys": [],
"columns": [
{ "columnName": "product_id", "dataType": "string", "validations": [{ "type": "required" }, { "type": "nonNull" }] },
{ "columnName": "product_name", "dataType": "string", "validations": [{ "type": "required" }, { "type": "maxLength", "value": 255 }] },
{ "columnName": "discount_pct", "dataType": "numeric", "precision": 5, "scale": 2, "validations": [{ "type": "required" }, { "type": "range", "min": 0, "max": 100 }] }
],
"isRolledOut": true,
"rolloutState": "SUCCESS",
"targetSchemaName": "STAGE"
},
{
"objectName": "C_sales_orders_v1",
"primaryKeys": ["order_id"],
"uniqueKeys": [],
"foreignKeys": [],
"columns": [
{ "columnName": "order_id", "dataType": "string", "validations": [{ "type": "required" }, { "type": "nonNull" }] },
{ "columnName": "amount", "dataType": "numeric", "precision": 10, "scale": 2, "validations": [{ "type": "required" }, { "type": "nonNull" }] },
{ "columnName": "placed_at", "dataType": "timestamp", "validations": [{ "type": "required" }, { "type": "nonNull" }, { "type": "timestampFormat", "format": "YYYY-MM-DD HH:MI:SS" }] }
],
"isRolledOut": true,
"rolloutState": "SUCCESS",
"targetSchemaName": "STAGE"
}
]
}
The objectName value is what you use as the URL path on POST /api/v2/objects/{objectName} when ingesting into the table — including the C_/c_ prefix for custom tables.
Status codes
200 OK— schema returned404 Not Found— no solution with that name has been rolled out for the calling tenant
- Lifecycle overview
- Save a standard schema
- Payload
- Schema definition structure
- Example request
- Response (201 Created)
- Status codes
- Roll out a schema to a tenant solution
- Payload
- Example request
- Response (201 Created)
- Status codes
- Audit columns added at rollout
- Upgrade a rolled-out schema to a new version
- Payload
- Example request
- Response (200 OK)
- Status codes
- Add a new column
- Path parameters
- Automatic column-name prefix on standard tables
- Payload
- Controlling required-ness and nullability on the new column
- Example request
- Response (200 OK)
- Status codes
- Create a custom table
- List solutions for the tenant
- Response (200 OK)
- Describe a solution's schema
- Query parameters
- Response (200 OK)
- Status codes