Loads
8. Loads¶
A load adds rows to one lake table. Four steps:
- Create the load with
POST /load. You get aloadId. - Upload one or more parquet files with
POST /load/{load_id}/file, then upload each file to the link it returns. - Commit the load with checksums using
POST /load/{load_id}/commit. - Poll
GET /load/{load_id}untilSUCCESSorFAIL.
Either all rows are added or none. Only you can see or change your loads.
8.1 Load status¶
| Status | Meaning |
|---|---|
OPEN | Created, not committed. You can still upload files. |
PROCESSING | Committed and being validated. |
SUCCESS | All rows were written to the lake table. |
FAIL | Nothing was written. ErrorType and ErrorMessages say why. |
Poll every 5 seconds while the status is PROCESSING.
8.2 POST /load: create a load¶
URL: POST https://<org>.api.comodash.io/v2/load | Scope: load:write
SDK equivalent: Load(config, load_type="APPEND_ONLY", table_name=...)
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
load_type | string | yes | Must be APPEND_ONLY. Rows are added, never changed or removed. |
table_name | string | yes | Target lake table. Lowercase letters, numbers and underscores. Cannot start or end with underscore. Created on first load. |
load_as_service_client_id | string | no | All rows belong to this service client. Files must not then have a service_client_id column. Send as a string, e.g. "12". |
partitions | string[] | no | Extra partition columns. Up to 100 distinct value combinations per load. |
Response 202
{"loadId": "20261013095801_355d42f6_0684_4b8f_b274_225bf7237494"}
Key errors: 400 MissingParameterError for missing fields; 400 InvalidParameterError: invalid load_type parameter (only APPEND_ONLY is accepted); 400 InvalidParameterError: table_name - Value should be in lowercase and contain only lowercase letters and underscores.
load_as_service_client_id is not verified here. If you cannot access that service client, the commit fails later with ErrorType: PermissionsError.
LOAD_ID=$(curl -s -X POST "$API/load" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"load_type":"APPEND_ONLY","table_name":"policies","load_as_service_client_id":"12"}' \
| jq -r .loadId)
r = requests.post(f"{API}/load", headers=HEADERS, json={
"load_type": "APPEND_ONLY", "table_name": "policies",
"load_as_service_client_id": "12",
})
r.raise_for_status()
load_id = r.json()["loadId"]
8.3 POST /load/{load_id}/file: upload a file¶
Returns a pre-signed S3 URL and temporary credentials for uploading one parquet file. Call once per file. The credentials expire after 1 hour.
URL: POST https://<org>.api.comodash.io/v2/load/{load_id}/file | Scope: load:write
SDK equivalent: Load.upload_file(data, ...) or Load.upload_df(df, ...)
Optional request body field
| Field | Type | Description |
|---|---|---|
file_key | string | Custom filename stem. Lowercase, letters, numbers, underscores. Uploading to the same file_key twice overwrites the first file (idempotent). |
Response 200
| Field | Type | Description |
|---|---|---|
presigned_url | object | Use as an HTTP POST to S3. Contains url and fields (AWSAccessKeyId, Policy, Signature, key, x-amz-security-token). |
sts_credentials | object | Temporary AWS credentials: AccessKeyId, SecretAccessKey, SessionToken. The SDK uses these with boto3. |
path | string | S3 object key. |
bucket | string | S3 bucket name. |
File requirements (enforced at commit time, not during upload):
- Must be a valid parquet file.
- Column names: only lowercase letters, numbers and underscores.
- If
load_as_service_client_idwas not given inPOST /load, files must include aservice_client_idcolumn. - If
load_as_service_client_idwas given, files must not include aservice_client_idcolumn.
Key errors: 400 InvalidParameterError: file_key - ... for a bad file_key; 404 NotFound: load_id does not exist for this user. for an unknown load ID.
8.4 POST /load/{load_id}/commit: commit the load¶
Starts the load pipeline. The status becomes PROCESSING. The load ID is the pipeline execution name, so calling this endpoint twice returns 400 InvalidParameter: Load Already Committed.
URL: POST https://<org>.api.comodash.io/v2/load/{load_id}/commit | Scope: load:write
SDK equivalent: Load.commit(check_sum={...})
Request body
{"check_sum": {"count(*)": 50000, "sum(face_amount)": 9876543210}}
check_sum is required and must have at least one entry. Keys are SQL aggregate expressions run over all uploaded rows. Values are what you expect. Accepts string, integer or float values.
Response 202: {"message": "Commit initiated."}
Key errors
| Status | Message | Cause |
|---|---|---|
| 400 | Invalid checksum data provided. | check_sum is not an object or has wrong types. |
| 400 | MissingParameter: missing check_sum parameter. At least one check_sum is required | The check_sum object is empty. |
| 400 | InvalidParameter: Load Already Committed. | Already committed. Check GET /load/{load_id}. |
| 404 | NotFound: load_id does not exist for this user. | Wrong load ID or not created by you. |
curl -s -X POST "$API/load/$LOAD_ID/commit" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"check_sum": {"count(*)": 50000}}'
requests.post(f"{API}/load/{load_id}/commit", headers=HEADERS,
json={"check_sum": {"count(*)": 50000}}).raise_for_status()
8.5 GET /load/{load_id}: get load status¶
URL: GET https://<org>.api.comodash.io/v2/load/{load_id} | Scope: load:read
SDK equivalent: Load.wait_to_complete(), Load.get_load_info()
Response 200
| Field | Type | Description |
|---|---|---|
LoadStatus | string | OPEN, PROCESSING, SUCCESS or FAIL. |
ErrorType | string | Only present on FAIL. |
ErrorMessages | array | Only present on FAIL. Items can be strings or objects with ErrorType, ErrorMessage and CheckResult. |
ErrorType values
| ErrorType | Cause |
|---|---|
NoFilesUploaded | No files were uploaded before the commit. |
ParquetValidation | File is not valid parquet, or has column names with disallowed characters. |
ServiceClientError | service_client_id is missing from both files and load_as_service_client_id, or present in both, or a row belongs to a service client you cannot access. |
CheckSumError | An aggregate value does not match your expected value. The message names the aggregate, expected and actual. |
ValidationError | Columns do not match the existing lake table (type mismatch, extra or missing columns). |
TooManyPartitions | More than 100 distinct partition value combinations. |
PermissionsError | load_as_service_client_id is not a service client you can access. |
InternalError | Unexpected internal error. Contact support. |
{
"LoadStatus": "FAIL",
"ErrorType": "CheckSumError",
"ErrorMessages": [
{
"CheckResult": "FAIL",
"ErrorType": "CheckSumError",
"ErrorMessage": "Checksum count(*) was expecting 50001 and got 50000"
}
]
}
import time
while True:
r = requests.get(f"{API}/load/{load_id}", headers=HEADERS).json()
print(r["LoadStatus"])
if r["LoadStatus"] not in ("OPEN", "PROCESSING"):
break
time.sleep(5)
if r["LoadStatus"] != "SUCCESS":
raise RuntimeError(r.get("ErrorMessages"))
8.6 What happens after commit (pipeline overview)¶
- Files are collected and validated (parquet format and column names).
- A temporary Athena table is created from all uploaded files.
- Service client rules are enforced per row.
- Checksums are calculated and compared with your expected values.
- The lake table is created (if new) or columns are matched (if it exists). Column types must match.
- Rows are inserted. Every row gets a
dash$load_idcolumn. The table is partitioned byservice_client_id,dash$load_idand any extrapartitionsyou requested.