On this page

The API

Your servers ask Misata for a dataset and get back connected tables whose stated numbers hold exactly. Use it to fill empty states and demos in your product, give each user a sandbox, or build test data on demand.

Base URL: https://api.demo.misata.studio/v1. Every request sends a Misata key.

Get a key#

In Studio, open API in the sidebar and create a key. It is shown once. Keep it on your server: anyone holding it builds datasets on your account. Revoke it there at any time and access ends at once.

Shell
export MISATA_KEY=msk_...

Start a dataset#

Describe it in words, give your own tables with the numbers they must hold, or name a built-in business.

Shell
curl https://api.demo.misata.studio/v1/datasets \
  -H "Authorization: Bearer $MISATA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: demo-acme-42" \
  -d '{
    "prompt": "A B2B SaaS with three plans over the last two years. MRR grows from $50,000 to $200,000, with 2% monthly churn spiking to 5% in Q3 2025.",
    "webhook_url": "https://your.app/hooks/misata",
    "external_user": "acme-42"
  }'

It answers 202 at once with the dataset's id and status: "running", and the build carries on. A retry with the same Idempotency-Key within a day returns the same dataset and is not charged again.

FieldWhat it is
promptThe data in words. Every number in it is held exactly or said not held.
instructionsHow this dataset should be made, beside what it is about ("company names fictional", "amounts in euros").
schemaYour own tables ({"tables": [...]}) with the numbers they must hold and a window of months.
ddlYour own schema as SQL DDL, Prisma, dbt, DBML and more.
templateA built-in business: saas_sales, ecommerce, fintech, healthcare, aml, iot_telemetry, education, and more.
storyA template's stated months, rates and events.
as_ofThe data's last day, YYYY-MM-DD; today when not given.
seedThe same request and seed make the same data.
webhook_urlAn https address told when the dataset is ready or failed.
external_userYour own id for whoever it is for; returned with the dataset, never read.

Follow it#

Ask GET /v1/datasets/{id}?wait=60: the answer comes as soon as the dataset is ready or failed, or after 60 seconds with it still running, so a loop of plain requests follows it with no polling interval to tune. Or stream its steps as server-sent events:

Shell
curl -N https://api.demo.misata.studio/v1/datasets/$ID/events -H "Authorization: Bearer $MISATA_KEY"

Events are numbered; reconnect with ?after=<n> or Last-Event-ID to pick up where you left off. When it is ready:

JSON
{
  "id": "8e4de4426abe", "status": "ready", "verified": true,
  "tables": {"accounts": 168, "subscriptions": 269, "invoices": 2728},
  "stated_numbers": {"held": 3, "total": 3, "not_held": []}
}

verified is true only when every stated number and every rule held. Anything not held is listed in not_held.

Take the data#

Files. One table as Parquet or CSV, or every table in one export:

Shell
curl "https://api.demo.misata.studio/v1/datasets/$ID/tables/subscriptions?format=csv" -H "Authorization: Bearer $MISATA_KEY"
curl -X POST https://api.demo.misata.studio/v1/datasets/$ID/exports -H "Authorization: Bearer $MISATA_KEY" \
  -H "Content-Type: application/json" -d '{"format": "sql", "dialect": "postgres"}'

An export returns a link valid for 15 minutes that needs no key. Formats include csv, parquet, sql (Postgres, MySQL, SQL Server, Snowflake, BigQuery, ClickHouse), duckdb, sqlite, dbt, and salesforce and hubspot (each CRM's own objects with a loader script; see Salesforce and HubSpot).

Into your database. Misata loads PostgreSQL or ClickHouse directly. Plan first: it checks the host and says what would be loaded, without touching it.

Shell
curl -X POST https://api.demo.misata.studio/v1/datasets/$ID/load -H "Authorization: Bearer $MISATA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target": "postgres", "url": "postgres://loader:...@db.your.app:5432/analytics", "schema": "demo"}'

Send "apply": true to load it. PostgreSQL gets every table in one transaction with its foreign keys enforced, so a failed load leaves nothing behind. A load writes only the dataset's own tables, in the schema you name, and replaces them only with "replace": true. The connection string is used for that one load and never stored or logged. The database must be reachable on the public internet over an encrypted connection; give Misata a user that can write only to the schema it loads.

Into your CRM. Misata loads HubSpot or Salesforce directly, from the files its CRM export carries: companies, contacts, deals and their activities, each linked as the dataset links them. Plan first, then send "apply": true:

Shell
curl -X POST https://api.demo.misata.studio/v1/datasets/$ID/load -H "Authorization: Bearer $MISATA_KEY" \
  -H "Content-Type: application/json" -d '{"target": "hubspot", "token": "'$HUBSPOT_TOKEN'", "apply": true}'

Salesforce takes instance_url beside token. Records are upserted on their Misata id, so a second load updates the same records; the properties or fields the files need are created first unless "create_fields": false. The token is used for that one call and never stored. Fields and limits: Salesforce and HubSpot.

Query it in place. Your dashboards can read the dataset where it is, with nothing copied:

Shell
curl -X POST https://api.demo.misata.studio/v1/datasets/$ID/query -H "Authorization: Bearer $MISATA_KEY" \
  -H "Content-Type: application/json" -d '{"sql": "SELECT plan_name, count(*) AS subscriptions, sum(mrr) AS mrr FROM subscriptions GROUP BY 1 ORDER BY 3 DESC"}'

One read-only SELECT (DuckDB SQL) at a time, over the dataset's own tables, within a time and memory limit.

Change it#

A ready dataset is changed by a revision: a new version, checked before it is kept. The first is never touched.

FieldWhat it changes
instructionWhat to change, in words, as your user said it ("far fewer accounts", "refunds rarer in December").
storyA template dataset's stated months, rates and events, read from GET /v1/datasets/{id}/story.
curvesThe monthly curves the data follows: [{"metric", "points": {"YYYY-MM": value}}].
controlsNamed controls, as GET /v1/datasets/{id}/controls lists them with their ranges.
Shell
curl https://api.demo.misata.studio/v1/datasets/$ID/revisions -H "Authorization: Bearer $MISATA_KEY" \
  -H "Content-Type: application/json" -d '{"instruction": "There are far too many accounts; make it about 120"}'

It answers 202 with the revision's id. Follow it with GET /v1/datasets/$ID/revisions/$REV; it ends ready, unchanged (nothing needed to change, with why), discarded (the new version did not verify, so it was not kept) or failed. Every version also opens in Studio.

Instructions for every dataset#

Say once what every dataset on your account should keep to:

Shell
curl -X PUT https://api.demo.misata.studio/v1/instructions -H "Authorization: Bearer $MISATA_KEY" \
  -H "Content-Type: application/json" -d '{"text": "Company names fictional, never real brands. Amounts in US dollars."}'

Each new dataset, from Studio or your app, reads them as requirements and holds them or says which it could not.

Webhooks#

When a dataset is ready or failed, Misata posts to your webhook_url:

JSON
{"type": "dataset.ready", "dataset": {"id": "...", "status": "ready", "verified": true, "external_user": "acme-42"}}

Each post is signed. Read your secret from GET /v1/webhook-secret. Check the Misata-Signature header, written t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of "<t>.<raw body>" with the secret, and refuse a t more than five minutes old.

Python
import hmac, hashlib, time

def verified(secret: str, header: str, body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    mac = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, parts["v1"]) and abs(time.time() - int(parts["t"])) < 300

Misata tries three times, only over https, and follows no redirect.

Errors#

StatusMeaning
401No key, or the key is not valid or was revoked.
402The account's allowance does not cover the build; nothing was charged.
404No dataset with that id on this account.
409The dataset is still running or failed, or it cannot take that change.
422A field cannot be held; detail.field names it and detail.message says what to write.
429The account is at its limit; wait detail.retry_after seconds.

A key sees only its own account's datasets.

Prefer generating data visually?

Misata Studio lets you turn natural language prompts into relational schemas with exact outcome invariants, live preview tables, and one-click database seeding without writing code.

Last updated Edit this page.md