Run Synthesis on Your Data
Upload owned CSV, Parquet, JSON, JSONL, or relational JSON data, authorize an exact quote, and generate new rows with RadMah Synthesis. The delivered artifacts are accompanied by quality, privacy, provenance, and integrity evidence.
✦What you'll build
You will upload a source dataset, choose one of the three operating modes of the same learned Synthesis engine, review the maximum charge, run the job, download its primary CSV, inspect its quality and privacy reports, and verify the evidence bundle offline.
Before you start
| Requirement | Details |
|---|---|
| Source dataset | CSV, Parquet, JSON, JSONL, or relational JSON with at least 1,000 source rows in every table that the learned model must fit. Upload size is governed by your current plan and is shown by the upload flow. |
| Credits | Enough available credit or an enabled pay-as-you-go policy for the quote. The server refuses execution above your authorized maximum. |
| API key | Required only for SDK, CLI, or REST use. Create one under Settings → API Keys. |
One engine, three operating modes
Quality, Automatic, and Fast are control profiles of one learned Synthesis engine. They never select a hidden statistical fallback or route by industry, dataset name, column name, or category value.
| Mode | Intent | Control authority |
|---|---|---|
| Quality | Highest supported learned quality | You may set the epoch ceiling, seed, batch size, and learning rate. Held-out convergence may stop training earlier. |
| Automatic | Governed defaults | The server owns training controls and reports the exact epoch and charge ceilings in the quote. |
| Fast | Lower-latency iteration | The same learned engine runs with a bounded server-owned training ceiling. Quality and privacy are still measured. |
Upload and inspect the source dataset
In the customer application, open Datasets, upload the source, and check the detected row and column counts. Then use Run Synthesis on the dataset page, or open /synthesizeand select it there.
import os
from radmah_sdk import RadMahClient
client = RadMahClient(
api_key=os.environ["RADMAH_API_KEY"],
base_url=os.environ.get("RADMAH_API_URL", "https://api.radmah.ai"),
)
dataset = client.upload_dataset(
"customers_2026.csv",
description="Owned training dataset for Synthesis",
)
print(dataset.id, dataset.rows, dataset.columns_json)Configure the run
The Synthesis setup page exposes only controls that the API and worker consume. Choose Generate synthetic data for a customer artifact or Train a reusable model to preserve a versioned model for later runs. Both operations use the same source dataset, governed compute, exact quote, and maximum-charge authorization. Generation adds mode, output rows, the mandatory quality/privacy evidence contract, an explicit release policy, optional numeric output ranges, and an owned saved model. The reproducibility seed is explicit in every mode. Only Quality exposes the training ceiling, batch size, and learning rate; Automatic and Fast keep those training controls server-owned.
Dataset
Owned CSV, Parquet, JSON, JSONL, or relational JSON with 1,000+ source rows in every fitted table
Rows
1 to 10,000,000 primary-table rows per run; the quote includes the exact admitted allocation across every related table
Evidence
Mandatory measured quality, privacy, provenance, integrity, and release evidence; there is no reduced evidence tier
Compute
Automatic, eligible CPU, or eligible GPU capacity
Release policy
Minimum aggregate, per-column, and relationship source-normalized fidelity plus maximum membership advantage / excess linkage indicators; untouched privacy and raw-source comparisons remain in evidence
Output constraints
Optional numeric ranges bound to measured source support; fidelity uses the same bounded source reference while retaining the untouched comparison
Saved model
Optional verified checkpoint trained from this exact dataset
Seed
Exact reproducibility input in Quality, Automatic, and Fast modes
Training ceiling
Quoted maximum; held-out convergence may stop earlier
ℹRelational JSON keeps tables separate
Upload one JSON object whose keys are table names and whose values are row arrays. The setup page shows every table and binds numeric constraints to an exact table and column. Synthesis emits one primary CSV plus supporting CSVs for related entities, then validates generated foreign keys against generated parent keys before release.
train_quote = client.estimate_cost(
kind="train",
dataset_id=str(dataset.id),
mode="quality",
compute="auto",
epochs=2_400,
batch_size=512,
learning_rate=0.001,
)
training_job = client.submit_job_with_budget(
kind="train",
dataset_id=str(dataset.id),
seed=42,
mode="quality",
compute="auto",
options={
"epochs": 2_400,
"batch_size": 512,
"learning_rate": 0.001,
},
max_credits=int(train_quote["credits_required"]),
).wait(timeout=1_800)
model_sha256 = (training_job.result_summary or {}).get(
"checkpoint_artifact_sha256"
)
if training_job.status != "succeeded" or not model_sha256:
raise RuntimeError("Reusable Synthesis model was not produced")Review and authorize the quote
The setup page requests a server quote before enabling submission. It shows training and generation credits, the compute assignment, the training and runtime ceilings, available credits, and any pay-as-you-go ceiling. Submission binds that quote as the maximum authorized charge.
from radmah_sdk import (
SynthesisNumericConstraint,
SynthesisReleasePolicy,
)
release_policy = SynthesisReleasePolicy(
minimum_overall_fidelity=0.75,
minimum_column_fidelity=0.75,
minimum_bivariate_fidelity=0.60,
maximum_membership_advantage=0.20,
maximum_linkage_risk=0.05,
require_zero_exact_copies=True,
# Optional, off by default: refuse delivery if a delivered text value
# is shaped like a live identifier (see "Live-identifier control").
block_live_identifiers=False,
)
quote = client.estimate_cost(
kind="synthesize",
dataset_id=str(dataset.id),
rows=10_000,
mode="quality",
compute="auto",
epochs=2_400,
)
job = client.submit_job_with_budget(
kind="synthesize",
dataset_id=str(dataset.id),
rows=10_000,
seed=42,
mode="quality",
compute="auto",
options={
"epochs": 2_400,
"batch_size": 512,
"learning_rate": 0.001,
},
numeric_constraints=[
SynthesisNumericConstraint(
column="customer_age",
minimum=18.0,
maximum=90.0,
),
],
release_policy=release_policy,
# Optional: a successful owned training-job id from the exact dataset.
# checkpoint_source_job_id=str(training_job.id),
max_credits=int(quote["credits_required"]),
)
print(job.id, job.status, job.mode)⚠A quote is an authorization boundary
Do not submit a different mode, dataset, row count, compute class, or training ceiling under an earlier quote. The server recomputes the request and fails closed if the accepted authority no longer matches.
ℹLive-identifier control (optional, off by default)
Set block_live_identifiers to true in the release policy (SDK block_live_identifiers=True, CLI --block-live-identifiers, or the “Refuse delivery if values look like live identifiers” box on the setup page) to refuse delivery when any delivered text value is shaped like an email address, a US Social Security number, an NHS number, a payment card number, an IBAN or a telephone number. Values in formally reserved test ranges — example.com-style domains, 555-01xx telephone numbers, SSN area 000 — are allowed. A refused run publishes no data and no evidence, and the job names each column, how many values and which formats, never the values themselves.
Only text values are checked; a number stored in a numeric column is not. With the control off, the run is delivered as before and any detection is reported on the run (result_summary.release_policy_evaluation.value_safety) and in the sealed evidence as an observational record. This is a control you choose, not a platform guarantee that output contains no identifiers.
Monitor, cancel, or replay
The job page shows the public mode, progress, timestamps, typed failure details, and artifacts. Running jobs can be cancelled. Terminal jobs can be replayed with the same seed or rerun as a changed-seed variation.
# Quote, submit, wait, and download the primary customer artifact
rady synthesize \
--dataset "$DATASET_ID" \
--rows 10000 \
--mode quality \
--epochs 2400 \
--batch-size 512 \
--learning-rate 0.001 \
--seed 42 \
--compute auto \
--minimum-overall-fidelity 75 \
--minimum-relationship-fidelity 60 \
--maximum-membership-advantage 20 \
--maximum-linkage-indicator 5 \
--output synthesis.csv
# Operational lifecycle
rady jobs get "$JOB_ID"
rady jobs cancel "$JOB_ID"
rady jobs rerun "$JOB_ID"ℹTraining survives a worker restart
While a platform worker trains a learned Synthesis model, it writes epoch-boundary training snapshots to the job's own private storage. If that worker is lost, the platform takes the job over and continues training from the latest snapshot it can verify, so finished epochs are not trained again. A snapshot is used only when the new attempt runs the same build, on a host with the same device and CPU capability, from the same data, contract and settings; otherwise it is refused, the refusal is disclosed in result_summary.training_resume, and training restarts from its first epoch. Jobs on dedicated batch compute do not write snapshots.
# Any mode. Counts training-epoch time only (not data preparation,
# calibration or generation) and is checked after each completed epoch, so
# every model trains at least one epoch and may run past the cap by up to one
# epoch of each training phase. Not valid with checkpoint_source_job_id. A
# capped run's stop point depends on host speed; its saved model is the
# replay authority.
job = client.submit_job_with_budget(
kind="synthesize",
dataset_id=str(dataset.id),
rows=10_000,
seed=42,
mode="automatic",
compute="auto",
options={"max_training_minutes": 30},
release_policy=release_policy,
max_credits=int(quote["credits_required"]),
).wait(timeout=3_600)
print(job.training_stop_reason) # e.g. "training_time_cap_reached"
print(job.training_time_cap) # requested, reached, measured seconds
print(job.training_resume) # resumed after a worker loss, and from whereDownload data and inspect evidence
A successful Synthesis run has exactly one primary customer CSV. Relational runs also expose one supporting CSV per related entity. The job and evidence pages expose the same artifact authority, plus a native quality report, privacy explanation, provenance, and the complete evidence download. Parquet is download-only; readable CSV artifacts are previewed.
import json
from pathlib import Path
from radmah_sdk.verify import verify_evidence_bundle
artifacts = client.list_artifacts(job.id)
primary = [a for a in artifacts if a.artifact_type == "primary"]
if len(primary) != 1:
raise RuntimeError(f"expected one primary artifact, received {len(primary)}")
csv_bytes = client.download_artifact_bytes(job.id, primary[0].id)
Path("synthesis.csv").write_bytes(csv_bytes)
quality = client.get_utility_report(job.id)
privacy = client.get_privacy_report(job.id)
bundle_bytes = client.download_evidence_bundle(job.id)
Path("evidence_bundle.json").write_bytes(bundle_bytes)
verification = verify_evidence_bundle(json.loads(bundle_bytes))
if not verification.ok:
raise RuntimeError(verification.reason)
print(quality)
print(privacy)
print(verification.kind.value, verification.integrity)Every sealed table can also be exported as CSV, as an Excel workbook, or with every other table in one ZIP, from the job page's Export tables panel, the SDK, or the CLI. Each export is made from the sealed table and states the sealed SHA-256 it came from: a CSV export is the sealed file itself; an Excel export holds it cell for cell, storing numbers and dates as Excel values only where Excel keeps the exact value and everything else as the sealed text. A table beyond Excel's limits (1,048,575 rows, 16,384 columns, 32,767 characters per cell, or 100,000,000 cells) is refused, never cut; its CSV is always available. The ZIP carries the job's seal and a manifest naming each table's source and digest.
listing = client.list_table_exports(job.id)
for table in listing["tables"]:
print(table["name"], table["rows"], table["formats"]["xlsx"])
client.export_table(job.id, listing["tables"][0]["name"], "table.csv")
workbook = client.export_table(job.id, listing["tables"][0]["name"], "table.xlsx", format="xlsx")
print(workbook.source_sha256) # the sealed CSV this workbook came from
client.export_tables_zip(job.id, "tables.zip", format="csv")How to read the reports
| Evidence | What it tells you |
|---|---|
| Distribution similarity | How closely delivered synthetic columns match source marginals. |
| Relationship similarity | How well measured cross-column structure is preserved. |
| Downstream utility | Whether models trained on synthetic data transfer to held-out real data. |
| Privacy indicators | Exact-row overlap, distance, distinguishability, and empirical disclosure evidence. |
| Run authority | Dataset, mode, seed, workload, compute, pricing, code, artifact, and evidence hashes. |
ℹMeasured evidence is not a blanket guarantee
Interpret fidelity and privacy together and against your use case. Empirical privacy indicators are not described as formal differential privacy, and a sealed bundle proves integrity—not fitness for an untested downstream decision.
Optional: plan through ADS
The Agentic Data Scientist can plan the same Synthesis workflow when a larger analytical goal needs multiple steps. ADS must still identify the owned dataset, use the same public mode and sole learned engine, show the exact plan digest and maximum credit quote, and wait for explicit approval. It is an orchestrator—not an alternate synthesis engine.