Quick start
- Get a key. Sign in, open your dashboard and click Generate API key under API Access. Copy it: it is shown once.
- Start a dataset. Send one request describing the data. With
"wait": true, small datasets come back finished. - Download the file. Use the
download_urlfrom the response.
curl -X POST https://www.generaterealdata.com/api/v1/datasets \
-H "Authorization: Bearer $GRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "prompt",
"description": "An online shop with customers, products, orders and reviews",
"rows": 5000,
"format": "csv",
"use_ai": true,
"wait": true
}'import os, requests
resp = requests.post(
"https://www.generaterealdata.com/api/v1/datasets",
headers={"Authorization": f"Bearer {os.environ['GRD_API_KEY']}"},
json={
"mode": "prompt",
"description": "An online shop with customers, products, orders and reviews",
"rows": 5000,
"format": "csv",
"use_ai": True,
"wait": True
},
timeout=70,
)
print(resp.status_code, resp.json())const resp = await fetch("https://www.generaterealdata.com/api/v1/datasets", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GRD_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"mode": "prompt",
"description": "An online shop with customers, products, orders and reviews",
"rows": 5000,
"format": "csv",
"use_ai": true,
"wait": true
}),
});
console.log(resp.status, await resp.json());Authentication
Send your key in the Authorization header of every request:
Authorization: Bearer grd_live_xxxxxxxxxxxxEach account has one key. If it leaks, click Regenerate key on your dashboard: the old key stops working immediately. Keep keys out of front-end code and public repositories; use an environment variable such as GRD_API_KEY.
https://www.generaterealdata.com/api/v1Endpoints
| Method | Path | What it does |
|---|---|---|
| POST | /api/v1/datasets | Start a dataset from a prompt, fields, SQL or a saved schema |
| GET | /api/v1/datasets/{id} | Status, rows, AI-generated fields and file information |
| GET | /api/v1/datasets/{id}/download | Download the file (?file=db for the SQLite file of an sql dataset) |
| GET | /api/v1/datasets?limit=10 | Your most recent datasets (max 50) |
| GET | /api/v1/schemas | Saved schemas |
| GET | /api/v1/schemas/{id} | One saved schema, to inspect or edit |
| DELETE | /api/v1/schemas/{id} | Delete a saved schema |
| GET | /api/v1/account | Plan, records and AI-generated fields left, limits and the active dataset |
| GET | /api/v1/field-types | Every type accepted in fields mode (no key needed) |
Create a dataset
Everything starts with POST /api/v1/datasets. Choose the input that suits you; the response is the same for all of them.
designing only happens for prompt, fields and sql modes, where our AI first turns your input into a schema. A failed dataset always has its records and AI-generated fields refunded.
From a prompt
Describe the data in plain English. The AI designs the tables, columns and relationships for you.
curl -X POST https://www.generaterealdata.com/api/v1/datasets \
-H "Authorization: Bearer $GRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "prompt",
"description": "An online shop with customers, products, orders and reviews",
"rows": 5000,
"format": "csv",
"use_ai": true,
"wait": true
}'import os, requests
resp = requests.post(
"https://www.generaterealdata.com/api/v1/datasets",
headers={"Authorization": f"Bearer {os.environ['GRD_API_KEY']}"},
json={
"mode": "prompt",
"description": "An online shop with customers, products, orders and reviews",
"rows": 5000,
"format": "csv",
"use_ai": True,
"wait": True
},
timeout=70,
)
print(resp.status_code, resp.json())const resp = await fetch("https://www.generaterealdata.com/api/v1/datasets", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GRD_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"mode": "prompt",
"description": "An online shop with customers, products, orders and reviews",
"rows": 5000,
"format": "csv",
"use_ai": true,
"wait": true
}),
});
console.log(resp.status, await resp.json());From a list of fields
List exactly the columns you want. Use table to group fields into tables of one database, and notes for rules and relationships. Types come from the field type list of 232 data types; use Auto-Detect to let us infer one from the name.
It is one of the largest built-in type libraries of any test data generator, and 222 of the types have their own built-in generator, so their values are always valid: real airport and ICD-10 codes, VINs, IBANs and card numbers with correct check digits, real cities with matching postcodes and time zones. Fields that belong together in a row are linked automatically (more than 200 links): the city is in the row's country, the email follows the name, the age follows the date of birth, the last 4 digits follow the card number. Your notes always come first, and rules the design writes from your description are kept.
curl -X POST https://www.generaterealdata.com/api/v1/datasets \
-H "Authorization: Bearer $GRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "fields",
"description": "A small CRM",
"rows": 2000,
"format": "excel",
"fields": [
{
"table": "customers",
"name": "customer_id",
"type": "Auto-Detect",
"notes": "primary key"
},
{
"table": "customers",
"name": "full_name",
"type": "Full Name"
},
{
"table": "customers",
"name": "email",
"type": "Email Address",
"notes": "unique"
},
{
"table": "orders",
"name": "order_id",
"type": "Auto-Detect",
"notes": "primary key"
},
{
"table": "orders",
"name": "customer_id",
"type": "Auto-Detect",
"notes": "references customers.customer_id"
},
{
"table": "orders",
"name": "order_total",
"type": "Price"
},
{
"table": "orders",
"name": "delivery_note",
"type": "AI Generated",
"notes": "short note from the courier"
}
]
}'import os, requests
resp = requests.post(
"https://www.generaterealdata.com/api/v1/datasets",
headers={"Authorization": f"Bearer {os.environ['GRD_API_KEY']}"},
json={
"mode": "fields",
"description": "A small CRM",
"rows": 2000,
"format": "excel",
"fields": [
{
"table": "customers",
"name": "customer_id",
"type": "Auto-Detect",
"notes": "primary key"
},
{
"table": "customers",
"name": "full_name",
"type": "Full Name"
},
{
"table": "customers",
"name": "email",
"type": "Email Address",
"notes": "unique"
},
{
"table": "orders",
"name": "order_id",
"type": "Auto-Detect",
"notes": "primary key"
},
{
"table": "orders",
"name": "customer_id",
"type": "Auto-Detect",
"notes": "references customers.customer_id"
},
{
"table": "orders",
"name": "order_total",
"type": "Price"
},
{
"table": "orders",
"name": "delivery_note",
"type": "AI Generated",
"notes": "short note from the courier"
}
]
},
timeout=70,
)
print(resp.status_code, resp.json())const resp = await fetch("https://www.generaterealdata.com/api/v1/datasets", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GRD_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"mode": "fields",
"description": "A small CRM",
"rows": 2000,
"format": "excel",
"fields": [
{
"table": "customers",
"name": "customer_id",
"type": "Auto-Detect",
"notes": "primary key"
},
{
"table": "customers",
"name": "full_name",
"type": "Full Name"
},
{
"table": "customers",
"name": "email",
"type": "Email Address",
"notes": "unique"
},
{
"table": "orders",
"name": "order_id",
"type": "Auto-Detect",
"notes": "primary key"
},
{
"table": "orders",
"name": "customer_id",
"type": "Auto-Detect",
"notes": "references customers.customer_id"
},
{
"table": "orders",
"name": "order_total",
"type": "Price"
},
{
"table": "orders",
"name": "delivery_note",
"type": "AI Generated",
"notes": "short note from the courier"
}
]
}),
});
console.log(resp.status, await resp.json());From your SQL schema
Send your CREATE TABLE statements. Table and column names, types, keys and constraints are kept. Data statements are removed before processing and never stored.
curl -X POST https://www.generaterealdata.com/api/v1/datasets \
-H "Authorization: Bearer $GRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "sql",
"sql": "CREATE TABLE customers (id SERIAL PRIMARY KEY, name TEXT NOT NULL, city TEXT);\nCREATE TABLE orders (id SERIAL PRIMARY KEY, customer_id INT REFERENCES customers(id), total NUMERIC(10,2));",
"notes": "Mostly Indian cities",
"rows": 10000,
"format": "sql",
"dialect": "postgresql"
}'import os, requests
resp = requests.post(
"https://www.generaterealdata.com/api/v1/datasets",
headers={"Authorization": f"Bearer {os.environ['GRD_API_KEY']}"},
json={
"mode": "sql",
"sql": "CREATE TABLE customers (id SERIAL PRIMARY KEY, name TEXT NOT NULL, city TEXT);\nCREATE TABLE orders (id SERIAL PRIMARY KEY, customer_id INT REFERENCES customers(id), total NUMERIC(10,2));",
"notes": "Mostly Indian cities",
"rows": 10000,
"format": "sql",
"dialect": "postgresql"
},
timeout=70,
)
print(resp.status_code, resp.json())const resp = await fetch("https://www.generaterealdata.com/api/v1/datasets", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GRD_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"mode": "sql",
"sql": "CREATE TABLE customers (id SERIAL PRIMARY KEY, name TEXT NOT NULL, city TEXT);\nCREATE TABLE orders (id SERIAL PRIMARY KEY, customer_id INT REFERENCES customers(id), total NUMERIC(10,2));",
"notes": "Mostly Indian cities",
"rows": 10000,
"format": "sql",
"dialect": "postgresql"
}),
});
console.log(resp.status, await resp.json());Reuse a saved schema
Every dataset response includes a schema_id. Send it instead of a prompt, fields or SQL to get the same structure every time, without the AI design step. Ideal for test pipelines; add a seed for identical data too.
Designs you make on the website are saved too: they appear in My designs on your dashboard and in GET /schemas, so you can design once in the browser and run the same design from your code.
curl -X POST https://www.generaterealdata.com/api/v1/datasets \
-H "Authorization: Bearer $GRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "schema",
"schema_id": "sch_8f2aQz71LmXc",
"rows": 10000,
"format": "sql",
"seed": 42
}'import os, requests
resp = requests.post(
"https://www.generaterealdata.com/api/v1/datasets",
headers={"Authorization": f"Bearer {os.environ['GRD_API_KEY']}"},
json={
"mode": "schema",
"schema_id": "sch_8f2aQz71LmXc",
"rows": 10000,
"format": "sql",
"seed": 42
},
timeout=70,
)
print(resp.status_code, resp.json())const resp = await fetch("https://www.generaterealdata.com/api/v1/datasets", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GRD_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"mode": "schema",
"schema_id": "sch_8f2aQz71LmXc",
"rows": 10000,
"format": "sql",
"seed": 42
}),
});
console.log(resp.status, await resp.json());To change a saved schema, fetch it with GET /schemas/{id}, edit the JSON, and send it as "schema": {...}.
AI-written fields and AI-generated fields
Free text such as reviews, support tickets or notes can be written by AI so it matches each row: a 1-star review reads unhappy, a note mentions the right product. Each AI-written value uses one AI-generated field from your plan; every other value (names, emails, dates, IDs, numbers) comes from built-in generators and uses records only.
The API never decides this for you. Ask for AI explicitly:
- prompt: send
"use_ai": trueto let the designer pick the free-text fields to write with AI. - fields: give a field the type
AI Generated. Fields with any other type are never AI-written. - sql: list the columns in
"ai_fields": ["reviews.review_text"]. - schema: fields with an
llmrule are AI-written, exactly as saved.
Use "dry_run": true to see ai_rows_required before you run. AI-generated fields are charged only for values the AI actually wrote. If the AI service cannot write at least 90% of a field after several retries, the dataset fails with ai_text_incomplete and nothing is charged.
All parameters
| Parameter | Type | Used in | Description |
|---|---|---|---|
| mode | prompt | fields | sql | schema | Optional | How you describe the data. If omitted, it is inferred from which of description, fields, sql or schema_id you send. |
| rows | integer, 1 to 500,000 | Required | Total rows across all tables. Checked against your records and AI-generated fields before anything starts. The dataset is sized to this total; tables that follow another table's rows (a payment for every completed order) can make the result slightly larger, but you are never charged more records than the rows you asked for. |
| format | json | csv | excel | sql | Default json | csv with several tables arrives as a .zip with one CSV per table; excel has one sheet per table. |
| description | text, up to 4,000 chars | prompt, fields | What the data is for, in plain English. |
| fields | list of objects | fields | Each item: name and type (required), table and notes (optional). Relationships go in notes, e.g. "references customers.customer_id". Only fields of type "AI Generated" are written by AI. |
| use_ai | boolean, default false | prompt | Let the designer choose AI-written fields (reviews, notes, descriptions). Without it, every field uses a built-in generator and no AI-generated fields. |
| ai_fields | list of "table.column" | sql | The columns to write with AI, e.g. ["reviews.review_text"]. Every other column uses a built-in generator. |
| sql | text, up to 200,000 chars | sql | CREATE TABLE statements. Structure only: any INSERT/COPY data is removed and never stored. |
| notes | text | Optional | Extra instructions for prompt, fields and sql modes, e.g. "prices in INR". |
| schema_id | sch_... | schema | Reuse a schema from an earlier run for identical structure every time. |
| schema | object | schema | An edited schema (from GET /schemas/{id}) instead of schema_id. |
| dialect | postgresql | mysql | sqlserver | sqlite | format sql | Database the .sql file is written for. Defaults to the dialect of your DDL, else PostgreSQL. |
| include_schema | boolean, default true | format sql | Include CREATE TABLE statements in the .sql file. |
| drop_existing | boolean, default false | format sql | Add DROP TABLE before each CREATE TABLE. |
| seed | integer | Optional | Same seed and same schema give identical data. |
| anomalies | boolean | Optional | Inject realistic data-quality issues (nulls, outliers, typos). |
| anomaly_rate | 0.01 to 0.5, default 0.05 | Optional | Share of rows with anomalies, when anomalies is true. |
| name | text | Optional | Dataset name shown on your dashboard. |
| wait | boolean | Optional | Keep the request open up to about 50 seconds and return the finished dataset if it is ready by then. |
| dry_run | boolean | Optional | Validate everything and return the records and AI-generated fields needed, without generating or charging. For 20,000 rows or more with a known schema it also returns run: how the dataset will be made (one run, or parts in parallel) and the estimated minutes. |
| new_run | boolean | Optional | Run again even if an identical request was made in the last 10 minutes. |
Check the status
A new dataset returns 202 with status queued or designing:
{
"id": "Xq7f2LmA9cB31kTz",
"status": "queued",
"mode": "prompt",
"format": "csv",
"rows_requested": 5000,
"schema_id": "sch_8f2aQz71LmXc",
"credits_estimated": 5000,
"status_url": "https://www.generaterealdata.com/api/v1/datasets/Xq7f2LmA9cB31kTz",
"download_url": "https://www.generaterealdata.com/api/v1/datasets/Xq7f2LmA9cB31kTz/download"
}
Check it every few seconds until the status is ready or failed:
progress object such as {"ai_parts_done": 12, "ai_parts_total": 45}. Keys, relationships and every other value are final before the text is written.progress shows {"parts_done": 1, "parts_total": 3}. The data is exactly what one run would make: the same seed always gives the same dataset.curl https://www.generaterealdata.com/api/v1/datasets/Xq7f2LmA9cB31kTz \
-H "Authorization: Bearer $GRD_API_KEY"requests.get("https://www.generaterealdata.com/api/v1/datasets/Xq7f2LmA9cB31kTz",
headers={"Authorization": f"Bearer {key}"}).json()await (await fetch("https://www.generaterealdata.com/api/v1/datasets/Xq7f2LmA9cB31kTz", {
headers: { Authorization: `Bearer ${key}` },
})).json();{
"id": "Xq7f2LmA9cB31kTz",
"status": "ready",
"mode": "prompt",
"format": "csv",
"rows_requested": 5000,
"rows_generated": 5000,
"rows_per_table": {
"customers": 600,
"products": 150,
"orders": 1850,
"order_items": 2400
},
"credits_charged": 5000,
"ai_rows_charged": 1200,
"schema_id": "sch_8f2aQz71LmXc",
"seed": 1838400211,
"files": [
"zip"
],
"completed_at": "2026-10-09T09:01:12Z",
"expires_at": "2026-11-08T09:01:12Z",
"download_url": "https://www.generaterealdata.com/api/v1/datasets/Xq7f2LmA9cB31kTz/download",
"file_url": "https://firebasestorage.googleapis.com/v0/b/.../dataset_Xq7f2LmA9cB31kTz.zip?alt=media&token=..."
}
A failed dataset explains why, and its records and AI-generated fields are refunded:
| error.code | Meaning | What to do |
|---|---|---|
| design_failed | The AI could not design a valid schema from the input | Rephrase the description, simplify fields, or check the SQL. |
| design_limit_reached | The fair-use limit for new AI designs on this pass was reached | Nothing was charged; reuse a saved design (mode schema), or buy another pass. |
| schema_error / validation_error | The schema could not be generated as requested | The message names the column or rule to fix. |
| insufficient_credits_or_plan | Not enough rows or AI-generated fields for the designed schema, or the plan expired | The message says which limit; request fewer rows or AI fields, or upgrade. |
| ai_text_incomplete | The AI service wrote less than 90% of a field after several retries | Nothing was charged; retry in a few minutes. |
| timed_out | The run stopped before finishing | Retry, or request fewer rows. |
| internal_error | Unexpected error | Retry; records and AI-generated fields are always refunded. |
Download the file
There are two ways to get the file of a ready dataset:
- From code:
download_url. Call it with your API key; it redirects to the file, so follow the redirect (curl -Land Python do this for you; in JavaScript follow it without the key header, as in the example below). It works as often as you like while the file exists. - In a browser:
file_url. Opens or downloads the file directly, no key needed. Treat it like a private share link: anyone you give it to can download that one file until it is deleted after 30 days. SQL datasets also havefile_url_dbfor the SQLite file.
status_url or download_url in a browser shows a "this link works from code" page, because browsers do not send your API key. Use file_url or your Dashboard to view data in a browser.curl -L -o data.zip \
-H "Authorization: Bearer $GRD_API_KEY" \
https://www.generaterealdata.com/api/v1/datasets/Xq7f2LmA9cB31kTz/downloadwith requests.get("https://www.generaterealdata.com/api/v1/datasets/Xq7f2LmA9cB31kTz/download",
headers={"Authorization": f"Bearer {key}"}, stream=True) as r:
r.raise_for_status()
with open("data.zip", "wb") as f:
for chunk in r.iter_content(65536):
f.write(chunk)// The address redirects to the file; follow it without the API key header.
const hop = await fetch("https://www.generaterealdata.com/api/v1/datasets/Xq7f2LmA9cB31kTz/download", {
headers: { Authorization: `Bearer ${key}` },
redirect: "manual",
});
const r = await fetch(hop.headers.get("location"));
await fs.promises.writeFile("data.zip", Buffer.from(await r.arrayBuffer()));- json: one array, or one object of arrays for several tables. csv: a .csv, or a .zip with one .csv per table. excel: one .xlsx with a sheet per table. sql: one .sql script for your database.
- For
format: "sql", add?file=dbto get the same data as a ready-to-open SQLite .db file. - Files are kept for 30 days. Datasets made through the API also appear on your dashboard.
Duplicates and safe retries
Networks fail. If your connection drops after you send a request, the dataset keeps generating on our side, and sending the same request again is safe:
- An identical request within 10 minutes does not start a second run and is not charged again. You get the original dataset back with
"repeat": true:
{
"id": "Xq7f2LmA9cB31kTz",
"status": "generating",
"repeat": true,
"message": "A dataset with exactly the same specification was already requested a moment ago, so this looks like an accidental duplicate. We have not started a new run and no extra rows or AI-generated fields were used. The original dataset is shown here. If you really want another run with this exact specification, send the request again with \"new_run\": true, or retry after 10 minutes.",
"original_requested_at": "2026-10-09T09:00:00Z"
}
- Need a fresh dataset with the same specification right away? Add
"new_run": true. - Lost the ID?
GET /datasetslists your most recent datasets. - Optional: send an
Idempotency-Keyheader with any unique value to protect retries for 24 hours. The Python helper does this for you.
Limits
| Limit | Value |
|---|---|
| Plans with API access | PRO, ULTRA, Enterprise |
| Starting datasets (incl. dry runs) | 5 per minute |
| Status, download and other requests | 60 per minute |
| Active datasets at a time | 1 per account |
| Rows per dataset | 1 to 500,000, within your plan's records |
| AI-generated fields | One per AI-written value, within your plan's AI-generated fields |
| SQL schema size | 200,000 characters |
| Fields per request | 300 |
| File retention | 30 days |
Each generated row uses one record from your plan, and each AI-written value one AI-generated field. Both are reserved when the run starts; whatever is unused is returned when it finishes, and everything is returned if it fails. Before a run starts, the rows you asked for and the AI-generated fields the plan needs are checked against your plan; if either is short, nothing starts and nothing is used. The dataset is sized to the rows you asked for, and you are never charged more than that, even when tables that follow another table's rows (a payment for every completed order) make it slightly larger.
In API responses, records keep their original field names credits_* and AI-generated fields ai_rows_* (for example credits_required and ai_rows_required), so existing code keeps working.
Errors
Errors use standard HTTP status codes and always have the same shape. field points to what to fix and hint suggests how:
{
"error": {
"code": "invalid_field_type",
"message": "Field 'email' has unknown type 'Emial Address'.",
"field": "fields[2].type",
"hint": "Did you mean 'Email Address'? See the list of types: GET /api/v1/field-types.",
"request_id": "req_4f9c2a71d0e8b613"
}
}
| HTTP | Code | Meaning | How to fix |
|---|---|---|---|
| 400 | invalid_json | Body is not a JSON object | Send Content-Type: application/json and a JSON object. |
| 400 | invalid_request | A parameter is missing, unknown or out of range | The error names the field; the hint suggests the fix. |
| 401 | missing_api_key / invalid_api_key | No key, wrong format, or a regenerated key | Send Authorization: Bearer grd_live_... with your current key. |
| 402 | insufficient_credits / insufficient_ai_rows | More rows, or more AI-generated fields, than your plan has left | Request fewer rows or fewer AI-written fields, or upgrade. Nothing was charged. |
| 403 | plan_required / plan_inactive | Plan without API access, or expired | API access is included with PRO, ULTRA and Enterprise. |
| 404 | dataset_not_found / schema_not_found / file_not_found | ID does not exist on this account | Check the ID; list yours with GET /datasets or /schemas. |
| 409 | run_in_progress | Another API dataset is still generating | Wait for active_dataset_id to finish, then retry. |
| 409 | not_ready / schema_not_ready | Download or schema requested too early | Poll the status until it is ready. |
| 410 | file_expired | File older than 30 days was deleted | Generate again; the same schema_id and seed reproduce the data. |
| 422 | invalid_fields / invalid_field_type / invalid_field_name / duplicate_field | A field is malformed | The error points to fields[n] and suggests the closest valid type. |
| 422 | invalid_sql / invalid_schema | The SQL has no CREATE TABLE, or the schema does not validate | Send structure-only DDL, or a schema from GET /schemas/{id}. |
| 422 | too_large_for_one_run | A very detailed schema at this many rows would take longer than a run can, even in parallel parts | Request rows_that_fit or fewer, or split the data into several datasets. Nothing was charged. |
| 429 | rate_limited | Too many requests this minute | Wait for Retry-After seconds. |
| 429 | design_limit_reached | The fair-use limit for new AI designs on this pass is reached | Reuse a saved design with mode schema and its schema_id, or buy another pass. |
| 500 | internal_error | Something went wrong on our side | Retry; quote the request_id if you contact support. |
Field types
Use these names as type in fields mode (not case-sensitive). The same list is available from GET /api/v1/field-types.
Generate with AI
Popular
Personal
Location
Commerce
Finance
Internet
Date & Time
Text
Numbers
Automotive
System
Healthcare
Education
HR
Logistics
Python helper
Copy this function to start a dataset, wait for it and save the file in one call. It adds an Idempotency-Key and retries once if the connection drops.
import os, time, uuid, requests
API = "https://www.generaterealdata.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['GRD_API_KEY']}"}
def generate(spec: dict, out_path: str) -> dict:
"""Start a dataset, wait for it, and save the file. Returns the final status."""
headers = {**HEADERS, "Idempotency-Key": str(uuid.uuid4())} # makes the retry below safe
for attempt in range(2):
try:
resp = requests.post(f"{API}/datasets", json={**spec, "wait": True}, headers=headers, timeout=70)
break
except requests.ConnectionError:
if attempt:
raise
data = resp.json()
if resp.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
while data["status"] not in ("ready", "failed"):
time.sleep(5)
data = requests.get(f"{API}/datasets/{data['id']}", headers=HEADERS, timeout=30).json()
if data["status"] == "failed":
raise RuntimeError(data["error"]["message"])
with requests.get(f"{API}/datasets/{data['id']}/download", headers=HEADERS, stream=True, timeout=300) as f:
f.raise_for_status()
with open(out_path, "wb") as out:
for chunk in f.iter_content(1 << 16):
out.write(chunk)
return data
generate({"mode": "prompt", "description": "A SaaS app with users and subscriptions",
"rows": 3000, "format": "csv"}, "saas_data.zip")request_id from any error.