Examples
Six concrete objectives, each worked end-to-end as a sequence of DPF API calls or MCP prompts.
Overview
Each objective below is self-contained but builds on the ones before it — together they walk a single workspace from account creation through a scheduled ingestion pipeline and into aggregated reporting. Run them in order the first time through — click any step to jump to it:
Conventions Used Below
- Base URL is
https://api.dpf-it.com. Every request/response is JSON. - Success responses are shaped
{ "success": true, "data": {...} }; errors are{ "error": { "code": "...", "message": "..." } }. YOUR_DPF_JWT_TOKENis thetokenfrom Objective 1's login call. Pass it asAuthorization: Bearer YOUR_DPF_JWT_TOKENon every authenticated request below.YOUR_WORKSPACE_IDis theworkspaceIdcreated in Objective 1 — reused by every other objective on this page.- Most endpoints (
/data-specs,/connections,/job-triggers,/workspaces) are multi-action: one URL, with anactionfield in the JSON body selecting the operation. - Full request/response schemas are in the API Docs; this page is the "how do these calls chain together" companion to that reference.
1Register a User & Verify OTP
Create an account, verify it with the one-time code emailed on registration, log in for a JWT, and create the workspace that every other objective on this page will load data into and query.
-
Register
termsAcceptedmust betrueor registration is rejected. This sends a 6-digit OTP to the given email.curl -X POST https://api.dpf-it.com/auth/register \ -H "Content-Type: application/json" \ -d '{ "email": "jane@example.com", "password": "SecurePassword123", "firstName": "Jane", "lastName": "Doe", "termsAccepted": true }' # 201 Created # { "success": true, "data": { "userId": "...", "email": "jane@example.com", ... } } -
Verify the OTP
Use the 6-digit code from the verification email. If it expired or never arrived, call
POST /auth/resend-otpwith just{"email": "..."}.curl -X POST https://api.dpf-it.com/auth/verify-otp \ -H "Content-Type: application/json" \ -d '{ "email": "jane@example.com", "otp": "123456" }' # 200 OK # { "success": true, "data": { "message": "Email verified successfully" } } -
Log in
Returns a 24h-lived access
tokenplus a ~90-dayrefreshToken(exchange it atPOST /auth/refreshonce the access token expires). Save thetoken— every call below sends it asAuthorization: Bearer ....curl -X POST https://api.dpf-it.com/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "jane@example.com", "password": "SecurePassword123" }' # 200 OK # { # "success": true, # "data": { # "userId": "550e8400-e29b-41d4-a716-446655440000", # "token": "eyJhbGciOiJIUzI1NiIs...", # "refreshToken": "q8c-BbPEpIF7ai-FrYUZ_a4acEk7finhMGHQ5vUvBvg", # ... # } # } -
Create a workspace
Creating a workspace also provisions its Iceberg catalog namespace — you'll need that
workspaceIdfor every remaining objective on this page.curl -X POST https://api.dpf-it.com/workspaces \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "create", "name": "Sales Analytics", "description": "Workspace used throughout the DPF examples" }' # 201 Created # { "success": true, "data": { "workspaceId": "550e8400-e29b-41d4-a716-446655440099", ... } }
-
Add DPF's MCP server to Claude
Settings → Connectors → Add custom connector → paste
https://api.dpf-it.com/mcpas the remote server URL. Claude registers itself as a client automatically — there's no separate credential to create first. - Log in when Claude redirects you On first use, Claude opens a browser to DPF's own login page. No account yet? The same page's sign-up form handles registration and OTP verification before sending you back to Claude — your password is typed there, never into a chat message.
- Nothing to copy or save Once you're redirected back, Claude stores its own access and refresh token and attaches it to every tool call automatically. There's no JWT to paste anywhere, and you won't be asked to log in again unless you revoke access or the refresh token itself expires.
-
Create a workspace
Ask, in plain English: "create a workspace called Sales Analytics." Claude calls
create_workspaceand every other objective on this page reuses it automatically.
workspaceId — for
550e8400-e29b-41d4-a716-446655440099 that's 446655440099. No lookup
call needed; it's a pure string operation on the ID you already have.
2Load a File into a New Table, Schema Inferred
Give DPF a sample file and set targetOption: "auto-infer" on the spec: AI infers
the target schema, generates parsing/transform code, and loads the sample — creating a
brand-new Iceberg table in the process. No pre-existing table or schema file needed.
-
Create the spec
Returns a presigned upload URL for the sample file (valid 1 hour, 100 KB max — a
representative sample is enough, DPF re-runs this same logic against full files later).
curl -X POST https://api.dpf-it.com/data-specs \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "create-spec", "workspaceId": "YOUR_WORKSPACE_ID", "specName": "Customer Signups", "sampleFileName": "customers.csv", "targetOption": "auto-infer", "computeSize": "small", "description": "Load new customer signups into a fresh Iceberg table" }' # 201 Created # { # "success": true, # "data": { # "specId": "550e8400-e29b-41d4-a716-446655440003", # "signedUrls": { "customers.csv": "https://dpf-specs.s3.us-east-1.amazonaws.com/...?X-Amz-Signature=..." }, # "expiresIn": 3600, # ... # } # } -
Upload the sample file
customers.csv:
PUT it straight to the signed URL from step 1 (it's a standard S3 presigned PUT, not a DPF endpoint — noid,name,email,signup_date 1,Acme Corp,ops@acme.test,2026-07-01 2,Beta Inc,hello@beta.test,2026-07-01 3,Globex LLC,contact@globex.test,2026-07-02Authorizationheader):curl -X PUT "SIGNED_URL_FROM_STEP_1" \ --upload-file customers.csv -
Start analysis
Kicks off schema inference + code generation, then (by default) loads the sample and starts
the Glue data-load job. Returns immediately — poll
get-statusfor progress.curl -X POST https://api.dpf-it.com/data-specs \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "start-analysis", "workspaceId": "YOUR_WORKSPACE_ID", "specName": "Customer Signups" }' # 202 Accepted # { "success": true, "data": { "specId": "...", "status": "processing", ... } } -
Poll the spec's status
Poll with
specIduntilstatusisready(orfailed). Onceready,lastJobIdpoints at the data-load run itself.curl -X POST https://api.dpf-it.com/data-specs \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "get-status", "specId": "550e8400-e29b-41d4-a716-446655440003" }' # 200 OK # { "success": true, "data": { "status": "ready", "lastJobId": "550e8400-e29b-41d4-a716-446655440005", ... } } -
Poll the load itself
Poll the same endpoint with
jobIdfor the Glue run's own progress and, once complete, its metrics.curl -X POST https://api.dpf-it.com/data-specs \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "get-status", "jobId": "550e8400-e29b-41d4-a716-446655440005" }' # 200 OK # { # "success": true, # "data": { # "status": "completed", # "metrics": { "recordsRead": 3, "recordsWritten": 3, "filesProcessed": 1 }, # ... # } # }
Describe the outcome in plain English. Claude picks the tools, uploads the sample file, and polls the job to completion — below is the real tool-call sequence from one such session.
Located customer.csv in the project folder (150-row sample) and
checked existing specs — WebLogDaily and WebLogs are
unrelated web-log pipelines, nothing for customer data yet.
customer.csv to that URL — a plain S3 PUT, not a DPF call — then continued.The load job kicked off on its own — polled that next.
Verified the result with SQL, directly over MCP:
customer Iceberg table, confirmed by query.
customers in this example)
with 4 audit columns appended automatically: dpf_line, dpf_filename,
dpf_job, dpf_ts. Confirm the table name with SHOW TABLES
in Objective 5 if you're not sure what it inferred.
3Load & Transform into an Existing Table
Same /data-specs endpoint as Objective 2, but with
targetOption: "existing-tables" instead of auto-infer: the target schema is the
customers table created in Objective 2, and the source file has
a different shape entirely — differently-named columns, a different date format, mixed casing.
additionalPrompt carries the field-mapping and transformation instructions, and
merge: true makes the load an upsert instead of a plain append, so re-running it
with overlapping IDs doesn't create duplicates — that requires the target table to already
have a primary key set (Step 1 below), which is what DPF upserts on.
-
Set a primary key on the target table
Because we're about to do
merge: trueloads againstcustomers, it needs a primary key for DPF to match rows on — a plain Iceberg table property (dpf.primary-keys), not adata-specscall. One-time per table — skip this if the table already has one (e.g. it was created via auto-infer withmerge: true, which sets this automatically; see Objective 2). Without it, the next step fails with400 MISSING_PRIMARY_KEYinstead of silently appending duplicate rows. The easiest way isALTER TABLE ... ADD PRIMARY KEYthrough the/queryendpoint — DuckDB and Iceberg don't support that syntax natively, so DPF handles it as a special case and sets the property directly:
(curl -X POST https://api.dpf-it.com/query \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "namespace": "446655440099", "query": "ALTER TABLE customers ADD PRIMARY KEY (id)" }' # 200 OK — { "success": true, "data": { "rows": [{ "result": "ALTER TABLE" }], ... } }ALTER TABLE customers DROP PRIMARY KEYremoves it the same way.) The same property can also be set at a lower level directly through the Iceberg REST Catalog (see API Docs) if you're already scripting against that endpoint:curl -X POST https://api.dpf-it.com/iceberg/v1/namespaces/446655440099/tables/customers \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "requirements": [], "updates": [ { "action": "set-properties", "updates": { "dpf.primary-keys": "id" } } ] }' # 200 OK — table metadata updated -
Create the spec against the existing table
curl -X POST https://api.dpf-it.com/data-specs \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "create-spec", "workspaceId": "YOUR_WORKSPACE_ID", "specName": "Legacy CRM Backfill", "sampleFileName": "legacy_crm_export.csv", "targetOption": "existing-tables", "targetTables": ["customers"], "merge": true, "computeSize": "small", "additionalPrompt": "Source columns are CUST_ID, COMPANY_NAME, EMAIL_ADDR, SIGNUP_MMDDYYYY. Map CUST_ID -> id, COMPANY_NAME -> name, EMAIL_ADDR -> email (lowercased), and parse SIGNUP_MMDDYYYY (MM/DD/YYYY) into signup_date as an ISO 8601 date. Upsert on id.", "description": "Backfill customers from the legacy CRM export" }' # 201 Created — same signedUrls / specId shape as Objective 2 -
Upload the sample file
legacy_crm_export.csv— note the different column names, casing, and date format vs. the targetcustomerstable:CUST_ID,COMPANY_NAME,EMAIL_ADDR,SIGNUP_MMDDYYYY 1,ACME CORP,OPS@ACME.TEST,07/01/2026 4,Initech,billing@initech.test,07/03/2026curl -X PUT "SIGNED_URL_FROM_STEP_1" \ --upload-file legacy_crm_export.csv -
Start analysis
curl -X POST https://api.dpf-it.com/data-specs \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "start-analysis", "workspaceId": "YOUR_WORKSPACE_ID", "specName": "Legacy CRM Backfill" }' -
Poll until ready, then poll the load
Same two-step polling as Objective 2 (
get-statusbyspecId, then by the resultinglastJobId). Becauseid: 1already exists incustomersfrom Objective 2, that row is updated in place (company casing normalized, email lowercased) —id: 4is inserted as new.
Ask Claude to load the file into the existing table with the field mapping you want. If the target table doesn't have a primary key set yet for the upsert, Claude sets one via the Iceberg REST endpoint before continuing — one prompt covers both steps.
Checked existing specs first — the customer table from Objective 2
already has signup_date/email columns, so this is a field
mapping, not a schema change.
customer has no primary key set, so merge/upsert has no key to match rows on — the same requirement as Step 1 in the API column above. The error came back with the exact call_dpf_api params to fix it.Set the primary key through the Iceberg REST endpoint — same operation as the
curl example above, via call_dpf_api (the MCP escape hatch
for endpoints without a dedicated tool):
legacy_crm_export.csv to that URL — a plain S3 PUT, not a DPF call — then continued.finish_data_source_onboarding again would re-attempt starting it (and could trigger a second load job), and to poll get_status instead. The agent did exactly that below — no repeat call, no duplicate job.One load job (b9b085f2-...) — polled it to completion:
Verified the mapping with SQL, directly over MCP:
customer with the requested
field mapping, confirmed by query.
existing-tables only determines column mapping against a schema that
already exists — it does not create tables. If customers didn't already exist,
this call would fail; that's exactly what Objective 2's auto-infer approach is for.
4Connect an SFTP Server & Trigger a Daily Load
Automate Objective 2's spec so new files dropped on an SFTP server load themselves every day,
with no manual create-job/start-job calls. A connection
holds how DPF authenticates to the server; a trigger pairs that connection with
a spec and a schedule.
-
Create the connection
DPF generates an RSA-4096 keypair and returns the public half — it never returns the private
key.
curl -X POST https://api.dpf-it.com/connections \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "create-connection", "workspaceId": "YOUR_WORKSPACE_ID", "type": "sftp", "hostname": "test-sftp.dpf-it.com", "username": "sftpuser" }' # 201 Created # { "success": true, "data": { "connectionId": "...", "publicKey": "ssh-rsa AAAA...", ... } } -
If
sftpuserdoesn't already exist, create it Skip this if the connection'susernameis an existing SFTP account on the server — it already has a.sshdirectory.sudo useradd -m sftpuser sudo -iu sftpuser mkdir -p ~/.ssh && chmod 700 ~/.ssh -
Install the public key on the SFTP server
As the connection's
usernameon the server.# As an administrator, become that user first: sudo -iu sftpuser echo 'ssh-rsa AAAA... ' >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys -
Test the connection
A connection must pass this before it can be used in a trigger.
curl -X POST https://api.dpf-it.com/connections \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "test-connection", "workspaceId": "YOUR_WORKSPACE_ID", "connectionId": "YOUR_CONNECTION_ID" }' # 200 OK — { "success": true, "data": { "success": true, ... } } -
Create the daily trigger
Points at the Objective 2 spec (
Customer Signups) — it must already have transformation code from a priorstart-analysis, which it does.preRules/postRulesare plain English; DPF compiles them into executable JS and returns it read-only aspreCode/postCode.postRulesis optional — omitted here, meaning "do nothing after load"; combined withdedupe: falsebelow, that's a deliberate trade-off (an untouched file reloads every run) rather than an oversight — see the MCP tab for the reasoning DPF walks through before making that same choice.curl -X POST https://api.dpf-it.com/job-triggers \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "create-trigger", "workspaceId": "YOUR_WORKSPACE_ID", "type": "sftp", "specName": "Customer Signups", "connectionId": "YOUR_CONNECTION_ID", "frequency": { "unit": "daily", "hourOfDay": 2 }, "dedupe": false, "preRules": "Only pick up *.csv files" }' # 201 Created — { "success": true, "data": { "triggerId": "...", "preCode": "...", "postCode": "...", ... } } -
(Optional) Fire it once immediately to test
Don't want to wait for 02:00 UTC to confirm it works end-to-end:
curl -X POST https://api.dpf-it.com/job-triggers \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "run-trigger-now", "workspaceId": "YOUR_WORKSPACE_ID", "triggerId": "YOUR_TRIGGER_ID" }' # 202 Accepted — the run shows up under list-trigger-runs on /workspaces
Describe the connection and schedule in plain English — server, username, and what "the customer load" refers to. If a detail is ambiguous, Claude asks before calling any DPF tool rather than guessing; when it needs the public key installed on the server, install it and reply to continue.
CustomerSignups and LegacyCRMImport, both plausible matches for "the customer load"1.8s"The customer load" was ambiguous between two specs, and the request didn't say what time to run or whether re-pulled files should be skipped — asked all three before touching any DPF API.
"Always reload" with no post-load action means an untouched file would load again every day — followed up on what should happen to a file after it loads, plus the file filter, before creating anything.
Every parameter was now explicit — spec, schedule, dedupe, post-load action, file filter — so it created the connection and trigger together in one call.
ba438616-... created; test failed — public key not yet installed on the server3.4ssftpuser's authorized_keys on test-sftp.dpf-it.com, then say when ready — it didn't retry or abandon the setup, just paused there with everything configured so far summarized.ba438616-...; test succeeded (4,200 files listed); trigger 64c90033-... created on a daily 02:00 UTC schedule2.4sOffered to fire the trigger immediately instead of waiting for tomorrow's 02:00 UTC run, to confirm the whole path works end-to-end.
740706ca-... started0.6sfrequency — no more
manual calls needed for day-to-day loads. Each run stamps its rows with dpf_ts,
which is exactly what the table-source spec in Objective 6 uses under
the hood to window each re-run to "what's new."
5Query the Fully Loaded Data
POST /query runs SQL directly against your Iceberg tables, scoped to a single
namespace — the trailing 12 characters of workspaceId (see the
callout in Objective 1). This objective covers reads — SELECT,
DESCRIBE, and SHOW TABLES; Objective 6 covers the write side.
-
Confirm the table name
curl -X POST https://api.dpf-it.com/query \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "namespace": "446655440099", "query": "SHOW TABLES" }' -
Select rows
curl -X POST https://api.dpf-it.com/query \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "namespace": "446655440099", "query": "SELECT * FROM customers ORDER BY dpf_ts DESC LIMIT 10" }' # 200 OK # { # "success": true, # "data": { # "columns": ["id", "name", "email", "signup_date", "dpf_line", "dpf_filename", "dpf_job", "dpf_ts"], # "rows": [ { "id": "4", "name": "Initech", "email": "billing@initech.test", ... } ], # "rowCount": 1 # } # } -
Count and aggregate
dpf_filename/dpf_jobmake it easy to see how much each load contributed:curl -X POST https://api.dpf-it.com/query \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "namespace": "446655440099", "query": "SELECT dpf_filename, COUNT(*) AS rows_loaded FROM customers GROUP BY dpf_filename ORDER BY rows_loaded DESC" }'
Just ask, in plain English — Claude finds the workspace and writes the SQL, so you never need
to look up a namespace. Ask a follow-up and it keeps going from there.
aws_edge_locations, cloudfront_log, cloudfront_log_daily, customer5.0scomment field and the dpf_line/dpf_filename/dpf_job/dpf_ts lineage columns for readability — noted they were available on request.customer.csv: 141 · legacy_crm_export.csv: 9 — 150 total2.9ssubmit_query
calls — schema explored, sample rows inspected, and per-file load counts confirmed,
without knowing a namespace ahead of time.
POST /query-large is a drop-in replacement with 4× the memory/CPU and a 120s
timeout (vs. 60s), for large scans or heavy joins/aggregations. It costs 4× the credits per call.
6Aggregate Loaded Data Automatically with a Table-Source Spec
This spec's source is an existing Iceberg table instead of an uploaded file: set
sourceType: "tables", and start-analysis generates a SQL query
(grouping/aggregating from
sourceTables) plus a deterministic INSERT/MERGE into
targetTables — DPF windows each run to rows added since the spec's own
last successful run automatically, so there's no dpf_ts bookkeeping to do by hand.
Paired with a spec_success trigger pointed at Objective 4's spec,
the aggregate re-runs itself the moment that day's load finishes — no external cron, Lambda, or
Airflow task required.
-
Create the table-source spec
No sample or format file to upload here —
sourceTablessupplies the data instead.targetOption: "auto-infer"has the AI design and createcustomer_signups_dailyfrom the query's own output shape (passtargetTablesinstead to pin an existing table's name, same as Objective 3).merge: truemakes re-runs upsert existing days instead of double-counting them.curl -X POST https://api.dpf-it.com/data-specs \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "create-spec", "workspaceId": "YOUR_WORKSPACE_ID", "specName": "daily-signups-aggregate", "sourceType": "tables", "sourceTables": ["customers"], "targetOption": "auto-infer", "merge": true, "additionalPrompt": "Count signups per day from the customers table, as columns signup_date and total_signups", "description": "Roll up customer signups into a daily summary table" }' # 201 Created # { "success": true, "data": { "specId": "550e8400-e29b-41d4-a716-446655440007", ... } } # No signedUrls — sourceType: tables never uploads sample/format files. -
Start analysis
Generates the aggregation query and the target table (first run), then executes it via the
query engine directly — no Glue job involved. The query is windowed to rows with
dpf_tsafter the spec's last successful run; on this first run that's the epoch, so it covers every row Objective 2–4 have loaded so far.curl -X POST https://api.dpf-it.com/data-specs \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "start-analysis", "workspaceId": "YOUR_WORKSPACE_ID", "specName": "daily-signups-aggregate" }' # 202 Accepted # { "success": true, "data": { "specId": "...", "status": "processing", ... } } -
Poll until ready, then poll the run
Same two-step polling as every other objective on this page —
get-statusbyspecId, then by the resultinglastJobId.curl -X POST https://api.dpf-it.com/data-specs \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "get-status", "specId": "550e8400-e29b-41d4-a716-446655440007" }' # 200 OK — { "success": true, "data": { "status": "ready", "lastJobId": "...", "sourceQuerySql": "...", "targetLoadSql": "...", ... } }sourceQuerySql/targetLoadSqlare always visible on the spec — the AI-generated query, same view-only guarantee as the Python code Objectives 2–3 generate. -
Create a
spec_successtrigger on Objective 4's spec Firesdaily-signups-aggregateautomatically every timeCustomer Signups(Objective 4's SFTP-triggered spec) finishes a successful job — nofrequency/connectionId, since it isn't polling anything itself.curl -X POST https://api.dpf-it.com/job-triggers \ -H "Authorization: Bearer YOUR_DPF_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "create-trigger", "workspaceId": "YOUR_WORKSPACE_ID", "type": "spec_success", "specName": "daily-signups-aggregate", "upstreamSpecName": "Customer Signups" }' # 201 Created — { "success": true, "data": { "triggerId": "...", "type": "spec_success", "upstreamSpecName": "Customer Signups", ... } }
Describe the rollup you want in plain English. Claude checks existing tables for a naming convention to follow, then asks how the summary should refresh and what it should contain — a table-source spec needs both decided explicitly, so answer those before it creates anything.
customer and an existing cloudfront_log_daily rollup4.8sChecked customer's columns, then looked at how the workspace's one
existing daily rollup was shaped, to follow the same convention rather than invent
a new one.
signup_date, acctbal, mktsegment1.3sTwo things the pattern here doesn't answer by itself — how the summary stays current, and what it actually contains — asked before creating anything.
Both answers map directly onto onboard_data_source's own
parameters — no separate manage_trigger call needed afterward.
9d885534-... created; wired to re-run whenever CustomerSignups finishes (trigger 04fa7bfb-...)3.1s72b9279b-... started11.3sVerified the resulting table, then went a step further and read back the
AI-generated targetLoadSql itself to confirm re-runs would add to
existing day/segment rows instead of overwriting them.
targetLoadSql does t.new_customers + s.new_customers (additive), not an overwrite0.4scustomer_daily — grouped by
signup_date × mktsegment, 143 rows on the initial load, wired
to re-run automatically whenever CustomerSignups finishes loading. Average
balance wasn't stored as its own column: the generator derives
total_acctbal / new_customers at query time instead, since a stored average
can't be merged additively the way a sum and a count can — confirmed by reading the
generated SQL back, not just assumed.
customers, this trigger
fires daily-signups-aggregate right after — each run's window picks up exactly the
rows the previous run hadn't seen yet. A schedule trigger (plain
frequency, no upstream spec) is the alternative if you'd rather re-run on a timer
than chain off a specific load.
spec_success and schedule triggers require their own
(downstream) specName to already be a sourceType: "tables" spec —
neither one supplies a file for a file-source spec to load, so daily-signups-aggregate
has to exist first. upstreamSpecName has no such restriction; it can be any spec,
file- or table-source.