API reference
Everything in the app is available over HTTPS with JSON. Create a key in Account settings → API keys and send it as a bearer token. Keys act as you; they can't manage keys, delivery buckets or your account.
curl https://useground.dev/api/datasets \ -H "Authorization: Bearer gk_…"
Python
The SDK wraps the whole flow: upload (with multipart for large files), wait for the analysis, export, download. Install it from the repository's sdk/python folder.
pip install ./sdk/python # or: pip install "ground @ git+https://github.com/<org>/ground#subdirectory=sdk/python"
from ground import Ground
g = Ground(api_key="gk_…", base_url="https://useground.dev")
ds = g.upload("patients.parquet", privacy_epsilon=None).wait()
print(ds.report["scores"]) # quality and privacy before you export
job = ds.export(rows=1_000_000, format="parquet", options={"classMix": {'"yes"': 0.5, '"no"': 0.5}})
job.wait().download("synthetic_patients.parquet")
df = job.to_pandas() # or straight into pandasErrors and limits
Errors are JSON { "error": "…" } with a status: 400 invalid input, 401 missing or revoked key, 402 not enough credits (the message says how many are needed and left), 403 not allowed (or your organization requires two-factor), 404 not found or not yours, 409 not ready yet, 429 too many exports running (3 at a time).
Upload or import data
Files go straight to storage: reserve a key, PUT the bytes (one request, or in parts above 64 MB), then create the dataset. Or skip the upload and give links.
/api/datasets/preflightOptional, before sending any bytes: checks the upload against your plan's size limit (413), related tables on your plan (403) and credits for the analysis (402). Nothing is charged.
{ "files": [{ "bytes": 52428800 }], "imports": 0 }{ "credits": 5, "available": 95, "uploadCapBytes": 1073741824 }/api/uploadsReserve a storage key for a file (CSV, TSV, Excel, JSON, JSON Lines or Parquet).
{ "filename": "patients.csv", "bytes": 52428800 }{ "key": "uploads/…/patients.csv", "url": "https://…", "headers": { "Content-Type": "text/csv" } }
// or, above 64 MB:
{ "key": "…", "multipart": { "uploadId": "…", "partSize": 16777216, "urls": ["https://…part1", "…"] } }<url>Send the file (or each part, in order, to its URL) with the returned headers. Keep each part's ETag response header.
/api/uploads/completeFinish a multipart upload.
{ "key": "…", "uploadId": "…", "parts": [{ "partNumber": 1, "etag": "\"9b2c…\"" }] }/api/datasetsCreate a dataset from uploads and/or links, and start the analysis. Several files are learned as related tables.
{
"uploads": [{ "key": "uploads/…/patients.csv", "filename": "patients.csv" }],
"imports": [{ "url": "https://bucket.s3.amazonaws.com/visits.parquet?X-Amz-Signature=…" }],
"privacy": { "epsilon": 3 }, // optional: (ε, δ)-differential privacy, single tables
"allowAiExamples": true // let Claude see scrubbed examples of text columns
}{ "id": "8d0f…" }Datasets
/api/datasetsYour datasets and those shared with your organizations.
/api/datasets/{id}Status (analyzing, ready, failed), live progress while analyzing, and once ready the columns, label, settings, model version and quality/privacy scores.
{ "status": "ready", "modelVersion": 3, "rows": 48842, "target": "income",
"columns": [{ "name": "age", "role": "model", "type": "number" }, …],
"report": { "scores": { "overall": 91, … }, "privacy": { "verdict": "pass", … } },
"options": { "rows": 10000, "seed": 1, … } }/api/datasets/{id}/refitAnalyze again with a different label, column roles, privacy budget, usefulness targets or entity column.
{ "target": "readmitted", "roles": { "notes": "text", "mrn": "id" }, "privacy": null, "utilityTargets": ["los_days"], "entityColumn": null }/api/datasets/{id}Save extension settings (see Options below), or rename.
{ "options": { … }, "name": "Readmissions v2" }/api/datasets/{id}/preview25 rows for these options, with text columns written.
{ "options": { … } }{ "rows": [ … ] }/api/datasets/{id}/versionsEvery analysis of the dataset; which one is active.
/api/datasets/{id}/versions/{version}Pin a version: previews and exports use it until the next analysis.
/api/datasets/{id}/versions/{version}/modelDownload the learned model as JSON (derived from your data: treat it like the source).
/api/datasets/{id}/orgShare with one of your organizations, or stop sharing.
{ "orgId": "org_…" } // or null/api/datasets/{id}/sourceDownload the original upload (the main table's, for related tables) while it is kept. Owner only; 410 once it has been deleted or has passed its retention date.
/api/datasets/{id}/sourceDelete the original upload(s) now. The learned model stays, so previews and exports keep working; re-analyzing doesn't.
/api/datasets/{id}Move the dataset to the trash. It can be restored until it is purged with all its files (see the trash routes); its exports can't be downloaded meanwhile.
/api/datasets/trashDatasets in the trash, with when each is purged for good. Those deleted before the trash existed have "legacy": true and no purge date: they can't be restored, only deleted forever.
/api/datasets/trash/{id}Restore a dataset from the trash (409 for a legacy one).
/api/datasets/trash/{id}Delete a trashed dataset forever: its uploads, training copies, models, reports and exports.
Exports
/api/jobsGenerate rows in the background as CSV, JSON Lines or Parquet. Related tables arrive as a zip with one file per table. Charges credits when it starts (the job's credits field); a failed export is refunded.
{ "datasetId": "8d0f…", "format": "parquet", "options": { "rows": 1000000, … }, "destinationId": null }{ "job": { "id": "…", "status": "queued", "rowsTotal": 1000000, … } }/api/jobs/{id}Status, rows done, the worker's current step, delivery status and model version.
/api/jobs/{id}/downloadRedirects to a short-lived download link for a finished file.
/api/jobs/{id}/cancelStop a queued or running export.
/api/jobsYour recent exports.
/api/destinationsYour delivery buckets (added in Account settings), for destinationId.
/api/creditsYour credit balance (this month's allowance and purchased credits), recent charges and refunds, and the price rules.
{ "balance": { "available": 3870, "monthlyLeft": 2870, "purchased": 1000, "resetsAt": "2026-11-01T00:00:00Z" }, "history": [ … ] }Designed datasets
Datasets made from a written request instead of an upload.
/api/datasetsStart one from a template or an empty spec.
{ "templateId": "ecommerce" } // or { "name": "Support tickets" }/api/datasets/{id}/designClaude turns the request into columns, distributions and relationships (design), re-syncs after you edit the request (rewrite), or applies one change (refine). Before the result is saved it looks up published rates (research, on by default), reads attached documents, has the design reviewed by a domain expert pass and calibrates it to cited targets. Runs in the background: returns 202 with a job id. A newer request replaces a design still running (refunded if it hadn't started); one sent while another is being queued gets 409.
{ "task": "design", "request": "Support tickets for a B2B SaaS: priority, product area, CSAT that drops with wait time…", "research": true, "documents": [ … ] }{ "jobId": "…", "status": "queued" }/api/datasets/{id}/design?job={jobId}The design's progress (status, step message) and, once it has succeeded, the reply, request, spec and what the pipeline found (facts, review, calibration).
{ "status": "succeeded", "reply": "…", "spec": { … }, "design": { "facts": [ … ], "review": { … }, "calibration": [ … ] } }/api/datasets/{id}/documentsAttach a data dictionary, codebook, protocol or paper for the next design (multipart form field "file"; PDF, text, Markdown, CSV or JSON, up to 4 MB, at most 5). Pass the returned document in the design's documents list.
{ "document": { "key": "…", "filename": "codebook.pdf", "mediaType": "application/pdf", "bytes": 182044 } }/api/datasets/{id}/design/reportThe design report, measured on a fresh sample: each target vs achieved with its source, each relationship asked vs achieved, label learnability and leakage, and the expert review.
/api/jobsExport it: pass the spec you want generated. Add whatIf to the spec for paired factual and counterfactual rows: each unit once as it is and once per scenario, with unit_id and scenario columns.
{ "datasetId": "…", "format": "csv", "spec": { …, "whatIf": { "scenarios": [{ "name": "everyone treated", "set": [{ "column": "treatment", "value": "drug A" }] }] } } }Options
What an export generates. Every field is optional except rows; omitted fields keep the dataset's saved settings.
{
"rows": 100000, // rows to generate (main table for related tables)
"seed": 1, // same seed + same model = same rows
"classMix": { "\">50K\"": 0.5, "\"<=50K\"": 0.5 }, // rebalance the label (JSON-encoded values)
"columnMix": { "column": "sex", "mix": { "\"Female\"": 0.5, "\"Male\"": 0.5 } },
"focus": { "column": "state", "value": "CA" }, // only rows with this value
"conditions": [{ "column": "age", "min": 65 }, { "column": "plan", "values": ["pro", "enterprise"] }],
"extendTime": { "column": "visit_date", "months": 6 }, // the months after the data ends
"nullRates": { "phone": 0.2 },
"rules": { "disabled": ["order:start<=end"], "custom": [] },
"flagColumn": true, // add is_synthetic = true
"writeText": true, // Claude writes free-text columns
"missingMarkers": true // CSV keeps the source's "?" / "N/A" placeholders
}