Loaders API
Loaders are inbound ELT (Extract, Load, Transform) configurations that pull data from external SaaS tools and databases into your data warehouse (Source). They complement Sources by bringing data into the warehouse, while Sources provide read access from the warehouse.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/v1/workspaces/{id}/loaders | List all loaders |
POST | /api/v1/workspaces/{id}/loaders | Create a loader |
GET | /api/v1/workspaces/{id}/loaders/{loaderId} | Get a loader |
PUT | /api/v1/workspaces/{id}/loaders/{loaderId} | Update a loader |
DELETE | /api/v1/workspaces/{id}/loaders/{loaderId} | Delete a loader |
POST | /api/v1/workspaces/{id}/loaders/{loaderId}/trigger | Trigger a sync run |
GET | /api/v1/workspaces/{id}/loaders/{loaderId}/runs | List loader runs |
GET | /api/v1/workspaces/{id}/loaders/{loaderId}/runs/{runId} | Get a loader run |
GET | /api/v1/loader-types | List available loader types (global catalog) |
POST | /api/v1/workspaces/{id}/loaders/test | Test a new loader connection |
POST | /api/v1/workspaces/{id}/loaders/{loaderId}/test | Test an existing loader connection |
POST | /api/v1/workspaces/{id}/loaders/discover | Discover available streams for new credentials |
POST | /api/v1/workspaces/{id}/loaders/{loaderId}/discover | Discover new streams for an existing loader |
POST | /api/v1/workspaces/{id}/loaders/{loaderId}/runs/{runId}/cancel | Cancel a running loader run |
List Loaders
GET /api/v1/workspaces/{id}/loaders
Returns all loaders in the workspace.
Response
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"workspace_id": "660e8400-e29b-41d4-a716-446655440000",
"source_id": "770e8400-e29b-41d4-a716-446655440000",
"name": "Service Cloud",
"loader_type": "salesforce_service_cloud",
"config": {
"instance_url": "https://mycompany.my.salesforce.com",
"api_version": "v59.0"
},
"target_schema": "SALESFORCE_RAW",
"selected_streams": ["Case", "CaseHistory", "CaseTeamMember", "Contact"],
"sync_mode": "incremental",
"stream_sync_modes": {
"Case": "incremental_merge",
"CaseTeamMember": "full_refresh",
"Contact": "incremental_merge"
},
"primary_keys": {},
"change_detection": {},
"schedule": "0 */6 * * *",
"status": "active",
"status_error": "",
"created_by": "880e8400-e29b-41d4-a716-446655440000",
"created_at": "2024-01-15T09:30:00Z",
"updated_at": "2024-01-15T09:30:00Z",
"last_run_at": "2024-01-15T15:00:00Z",
"last_run_id": "990e8400-e29b-41d4-a716-446655440000",
"last_run_status": "completed"
}
]In this example CaseHistory has no entry in stream_sync_modes, so it runs in the loader-wide sync_mode (incremental, append); the other three streams run in their own mode. See Sync modes.
Example
curl -X GET https://agentic.zeotap.com/api/v1/workspaces/{id}/loaders \
-H "Authorization: Bearer <token>" \
-H "X-Workspace-ID: <workspace-id>"Create Loader
POST /api/v1/workspaces/{id}/loaders
Creates a new loader to pull data from an external source into a warehouse.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name |
loader_type | string | Yes | Loader connector type (see List Loader Types) |
source_id | string (UUID) | Yes | Target warehouse source to load data into |
config | object | Yes | Connection configuration (varies by loader type) |
credentials | object | Depends on type | Secret credentials (API keys, tokens). For an OAuth loader, send {"credential_ref": "<ref>"} with the reference returned by the OAuth connect flow. Stored encrypted and never returned. |
target_schema | string | No | Schema in the warehouse to write tables to. Default: cdp_raw |
selected_streams | array of string | Yes | Names of the streams to load, as returned by Discover Streams |
sync_mode | string | No | Loader-wide default sync mode: full_refresh, incremental (append) or incremental_merge (merge on key). Default: incremental. Used by every stream with no entry in stream_sync_modes |
stream_sync_modes | object | No | Stream name → sync mode, for streams that run in a mode other than sync_mode. Omitted: filled in from the connector’s recommended mode for each selected stream (default_sync_mode on Discover Streams). Sent: used exactly as given — {} means every stream follows sync_mode |
primary_keys | object | No | Stream name → array of column names that incremental_merge upserts on, overriding the key the connector declares |
change_detection | object | No | Stream name → how the stream’s incremental reads detect change (warehouse loaders). See Change detection |
schedule | string | No | Cron expression (UTC). Empty or omitted: runs only when triggered |
Supported Loader Types
GET /api/v1/loader-types returns the full catalog, including each type’s configuration schema. Commonly used types:
Supported Loader Types
| Type | Description |
|---|---|
salesforce | Salesforce Sales Cloud objects |
salesforce_service_cloud | Salesforce Service Cloud objects |
hubspot | HubSpot CRM objects |
stripe | Stripe payment data |
shopify | Shopify e-commerce data |
ga4 | Google Analytics 4 |
facebook_ads | Facebook Ads campaigns and performance |
google_ads | Google Ads campaigns and performance |
linkedin_ads | LinkedIn Ads campaigns |
klaviyo | Klaviyo email marketing |
mailchimp | Mailchimp email marketing |
postgresql | PostgreSQL database |
mysql | MySQL database |
rest_api | Custom REST API |
google_sheets | Google Sheets |
s3 | Amazon S3 files |
Example
curl -X POST https://agentic.zeotap.com/api/v1/workspaces/{id}/loaders \
-H "Authorization: Bearer <token>" \
-H "X-Workspace-ID: <workspace-id>" \
-H "Content-Type: application/json" \
-d '{
"name": "Service Cloud",
"loader_type": "salesforce_service_cloud",
"source_id": "770e8400-e29b-41d4-a716-446655440000",
"config": {
"instance_url": "https://mycompany.my.salesforce.com",
"api_version": "v59.0"
},
"credentials": {"credential_ref": "<ref from the OAuth connect flow>"},
"target_schema": "SALESFORCE_RAW",
"selected_streams": ["Case", "CaseHistory", "CaseTeamMember", "Contact"],
"sync_mode": "incremental",
"schedule": "0 */6 * * *"
}'The request omits stream_sync_modes, so each selected stream gets the Service Cloud loader’s recommended mode, and the response stores only the streams whose mode differs from sync_mode:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"sync_mode": "incremental",
"stream_sync_modes": {
"Case": "incremental_merge",
"CaseTeamMember": "full_refresh",
"Contact": "incremental_merge"
},
"...": "remaining loader fields"
}To choose the modes yourself, send stream_sync_modes explicitly — it then replaces the recommendations entirely:
{
"sync_mode": "incremental_merge",
"stream_sync_modes": {"CaseHistory": "incremental"}
}Returns 201 Created with the loader object.
Get Loader
GET /api/v1/workspaces/{id}/loaders/{loaderId}
Returns a single loader by ID.
Update Loader
PUT /api/v1/workspaces/{id}/loaders/{loaderId}
Updates an existing loader configuration.
Request Body
| Field | Type | Required | When omitted |
|---|---|---|---|
name | string | Yes | — |
config | object | Yes | — (the full configuration is replaced) |
selected_streams | array of string | Yes | — (the list is replaced) |
target_schema | string | No | Reset to cdp_raw — send the current value to keep it |
credentials | object | No | Stored credentials kept. When sent, the fields you send are merged over the stored ones |
sync_mode | string | No | Stored value kept |
stream_sync_modes | object | No | Stored map kept. Send {} to clear it, so every stream follows sync_mode |
primary_keys | object | No | Stored map kept. Send {} to clear it |
change_detection | object | No | Stored map kept. Send {} to clear it |
schedule | string | No | Stored schedule kept. Send "" to clear it (manual runs only) |
Connector-recommended modes are applied only on create. An update never fills stream_sync_modes from the connector, so a loader’s tables only change mode when you change it. An entry for a stream you deselect is dropped along with the stream.
Example
Run CaseHistory as append and leave every other setting as stored:
curl -X PUT https://agentic.zeotap.com/api/v1/workspaces/{id}/loaders/{loaderId} \
-H "Authorization: Bearer <token>" \
-H "X-Workspace-ID: <workspace-id>" \
-H "Content-Type: application/json" \
-d '{
"name": "Service Cloud",
"config": {
"instance_url": "https://mycompany.my.salesforce.com",
"api_version": "v59.0"
},
"target_schema": "SALESFORCE_RAW",
"selected_streams": ["Case", "CaseHistory", "CaseTeamMember", "Contact"],
"stream_sync_modes": {
"Case": "incremental_merge",
"CaseHistory": "incremental",
"CaseTeamMember": "full_refresh",
"Contact": "incremental_merge"
}
}'stream_sync_modes is replaced as a whole, so send every entry you want to keep.
Returns 200 OK with the loader object.
Sync Modes
A stream’s effective sync mode is stream_sync_modes[stream] if the map has an entry for it, otherwise the loader’s sync_mode.
| Value | Behaviour |
|---|---|
full_refresh | Each run reads the stream in full and replaces the table |
incremental | Each run reads records changed since the stored cursor and appends them |
incremental_merge | Reads like incremental, then upserts on the stream’s primary key (primary_keys[stream], else the connector’s declared key, else an id column) |
Validation, applied on create and update (a violation returns 400):
- every key in
stream_sync_modes,primary_keysandchange_detectionmust be one ofselected_streams; - every value in
stream_sync_modesmust be one of the three modes above; change_detectionrules that depend on the sync mode are checked against each stream’s effective mode.
A stream that cannot be read incrementally (no cursor — supports_incremental: false on Discover Streams) is read in full on every run whatever its mode. Under incremental that appends a complete copy of the stream every run, so give such streams full_refresh (or incremental_merge, which keeps one row per key but never removes rows deleted at the source).
Loaders created before per-stream modes existed have stream_sync_modes: {} and run every stream in sync_mode.
Change Detection
change_detection maps a stream to an object describing how its incremental reads detect change. Streams with no entry read on a timestamp cursor, which is the default.
| Field | Values | Description |
|---|---|---|
mode | cursor (default), native_cdc | native_cdc reads the source warehouse’s own change feed (loader types that support it) |
cursor_field | column name | cursor mode only: the column the cursor compares, for loader types that let you choose it |
include | all_changes (default), inserts_only | native_cdc only. all_changes needs the stream’s effective mode to be incremental_merge |
on_delete | hard (default), soft, ignore | native_cdc with all_changes only: how a deletion is applied to the target |
native_cdc needs an incremental effective mode, and with incremental_merge it also needs primary_keys for the stream.
Delete Loader
DELETE /api/v1/workspaces/{id}/loaders/{loaderId}
Deletes a loader.
Response
{
"status": "deleted"
}Trigger Sync Run
POST /api/v1/workspaces/{id}/loaders/{loaderId}/trigger
Manually triggers a loader sync run.
Response
Returns the created loader run object with status 201 Created.
Example
curl -X POST https://agentic.zeotap.com/api/v1/workspaces/{id}/loaders/{loaderId}/trigger \
-H "Authorization: Bearer <token>" \
-H "X-Workspace-ID: <workspace-id>"List Loader Runs
GET /api/v1/workspaces/{id}/loaders/{loaderId}/runs
Returns the execution history for a loader.
Response
[
{
"id": "aae8400-e29b-41d4-a716-446655440000",
"loader_id": "550e8400-e29b-41d4-a716-446655440000",
"workspace_id": "660e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"started_at": "2024-01-15T15:00:00Z",
"completed_at": "2024-01-15T15:05:30Z",
"rows_loaded": 15420,
"streams_total": 2,
"streams_failed": 0,
"error_message": "",
"triggered_by": "880e8400-e29b-41d4-a716-446655440000",
"created_at": "2024-01-15T15:00:00Z"
}
]Loader Run Statuses
| Status | Description |
|---|---|
pending | Run is queued |
running | Run is in progress |
completed | Run finished successfully |
failed | Run failed with an error |
cancelled | Run was cancelled |
Get Loader Run
GET /api/v1/workspaces/{id}/loaders/{loaderId}/runs/{runId}
Returns details of a specific loader run.
Discover Streams
POST /api/v1/workspaces/{id}/loaders/discover (body: loader_type, config, credentials) or POST /api/v1/workspaces/{id}/loaders/{loaderId}/discover (stored configuration)
Returns the streams the loader can read with these credentials.
{
"streams": [
{
"name": "CaseHistory",
"label": "Case History",
"columns": [
{"name": "Id", "type": "string", "nullable": false},
{"name": "CreatedDate", "type": "timestamp", "nullable": true}
],
"cursor_field": "CreatedDate",
"supports_incremental": true,
"primary_key": ["Id"],
"default_sync_mode": "incremental"
}
]
}| Field | Description |
|---|---|
cursor_field | The column incremental reads track. Absent for a stream read in full every run |
supports_incremental | Whether the stream can be read incrementally |
primary_key | The columns that identify a row, used by incremental_merge unless primary_keys overrides them |
default_sync_mode | The connector’s recommended sync mode for the stream, applied when a loader is created without stream_sync_modes. Absent when the connector has no recommendation (the stream then follows sync_mode) |
List Loader Types
GET /api/v1/loader-types
Returns the list of available loader connector types with their configuration schemas and available streams. This endpoint is global and does not require the X-Workspace-ID header.
Loader Object
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique identifier |
workspace_id | string (UUID) | Owning workspace |
source_id | string (UUID) | Target warehouse source |
name | string | Display name |
loader_type | string | Connector type |
config | object | Connection configuration |
target_schema | string | Warehouse schema to write to |
selected_streams | array of string | Names of the streams the loader reads |
sync_mode | string | Loader-wide default sync mode: full_refresh, incremental or incremental_merge |
stream_sync_modes | object | Stream name → sync mode for streams that do not follow sync_mode. Always an object; {} when every stream follows sync_mode |
primary_keys | object | Stream name → merge-key columns overriding the connector’s key. Always an object |
change_detection | object | Stream name → change-detection settings. Always an object |
schedule | string | Cron expression |
status | string | draft, active, paused, or error |
status_error | string | Error message when status is error |
created_by | string (UUID) | Account that created the loader |
created_at | string (ISO 8601) | Creation timestamp |
updated_at | string (ISO 8601) | Last update timestamp |
last_run_at | string (ISO 8601) or null | Last run time |
last_run_id | string (UUID) or null | ID of the most recent run |
last_run_status | string | How the most recent run ended (list responses only) |
last_run_error | string | The most recent run’s error, if it failed (list responses only) |
Loader Run Object
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique identifier |
loader_id | string (UUID) | Parent loader |
workspace_id | string (UUID) | Owning workspace |
status | string | pending, running, completed, failed, or cancelled |
started_at | string (ISO 8601) | Run start time |
completed_at | string (ISO 8601) or null | Run completion time |
rows_loaded | integer | Total rows loaded |
streams_total | integer | Total streams processed |
streams_failed | integer | Number of failed streams |
error_message | string | Error details (if failed) |
triggered_by | string (UUID) or null | Account that triggered the run |
job_id | string or null | External job identifier |
created_at | string (ISO 8601) | Record creation time |