# Rowsmith > Generated fake test data from a table schema (CSV, JSON or SQL INSERTs), plus a catalogue of deliberately > invalid values. Free and rate-limited; no account and no key. Every value is generated by a program from a seed; > identifiers come only from reserved or invalid ranges. Terms: https://testdata-api.re-jam620.workers.dev/terms. Data notice: https://testdata-api.re-jam620.workers.dev/privacy. Send schemas only, never real data. ## Quickstart (60 seconds) 1. Add Rowsmith to Claude Desktop, Cursor or another MCP client: ```json {"mcpServers":{"rowsmith":{"command":"npx","args":["-y","rowsmith-mcp"]}}} ``` 2. Ask: "Generate 20 orders with a fixed seed as CSV" 3. Or call the API directly: ```sh curl -s https://testdata-api.re-jam620.workers.dev/v1/generate -H 'content-type: application/json' \ -d '{"rows":20,"seed":42,"format":"csv","columns":[{"name":"order_id","type":"sequence","prefix":"ord_","pad":4},{"name":"email","type":"email"},{"name":"total_usd","type":"decimal","min":1,"max":500}]}' curl -s 'https://testdata-api.re-jam620.workers.dev/v1/invalid?kind=email&count=5&seed=42' ID=$(curl -s https://testdata-api.re-jam620.workers.dev/v1/sandbox -H 'content-type: application/json' -d '{"seed":42}' | sed -E 's/.*"id":"([^"]+)".*/\1/') curl -s https://testdata-api.re-jam620.workers.dev/v1/sandbox/$ID/charges -H 'content-type: application/json' \ -d '{"amount":1999,"currency":"usd","card":"4242424242424242"}' ``` Python's standard-library urllib is blocked by Cloudflare before it reaches the API (403, error code 1010) when it sends its default User-Agent. Set your own, e.g. `User-Agent: my-tests/1.0`. In our tests on 2026-10-18, requests, httpx, aiohttp, Node and curl worked with their defaults. - Free, rate-limited, no account, no API key. - The same schema and seed give byte-identical output for the same generator version. - An invalid-values catalogue with the reason each value is invalid. - A payment sandbox that never moves money: a sandbox and everything in it is deleted 1 hour after it is created. It accepts only card numbers from Stripe's published testing list and stores no free text you send. - Listed on the official MCP Registry as io.github.meiorz/rowsmith. ## Determinism Every endpoint takes a seed and returns an output version (header X-Output-Version). The same request and seed give byte-identical output for the same output version: generator 1, catalogue 1. Send "version" to pin one. A pinned version is served as long as the service runs; a retired version answers 409. A version's output is not changed in place: a golden test checks its bytes on every build. ## Golden manifests (evals) Every /v1/generate and /v1/invalid answer carries X-Output-SHA256 (sha256 of the exact body bytes) and X-Request-SHA256 (sha256 of the canonical request: the JSON that X-Reproduce encodes, defaults filled in and version pinned, for a schema request the mapped columns, also on JSON answers; for /v1/invalid the path and query in its X-Reproduce, GET /v1/invalid?kind=&count=&seed=&version=). Computed per answer, nothing stored; empty_result answers carry neither. A manifest pins a dataset; an invalid-value suite is an entry for /v1/invalid (kind, count, seed and the catalogue version fix its bytes): {"rowsmith_manifest":1,"entries":[{"name":"orders-small","request_sha256":"2dec5a57b33f37e032f3c1a731c1eface990506131a3cee7e79cf989cdd19833","output_sha256":"5b0c5e92c590dcceb273acd30cbb652d51c8e5706495908bcac0dc496c71be4c","output_version":"1","schema_version":null,"reproduce":"POST /v1/generate body-b64url=eyJ0YWJsZSI6ImRhdGEiLCJyb3dzIjozLCJzZWVkIjo0MiwiZm9ybWF0IjoiY3N2IiwiY29sdW1ucyI6W3sibmFtZSI6ImlkIiwidHlwZSI6InV1aWQifSx7Im5hbWUiOiJlbWFpbCIsInR5cGUiOiJlbWFpbCJ9XSwidmVyc2lvbiI6IjEifQ"},{"name":"invalid-email-v1","request_sha256":"72d0ec1c9f4037f0fb061685e43b9d15485607df1636b46ace3351f7c70ad2c8","output_sha256":"b12c7a2377b5416953e509999ae337adf79044bf1224e77fb69fff40657eedcc","output_version":"1","schema_version":null,"reproduce":"GET /v1/invalid?kind=email&count=5&seed=42&version=1"}]} Fields: name (yours), request_sha256, output_sha256, output_version (X-Output-Version), schema_version (X-Schema-Version or null), reproduce (a request that regenerates the bytes by itself; for a JSON answer or the body-sha256= form, the request you sent with version pinned). Verify: send reproduce, compare the body's sha256 with output_sha256; a difference is drift. A pinned version is served as long as the service runs; a retired version answers 409. ## Endpoints - POST /v1/generate: schema in, rows out. Up to 500 rows, 50 columns, 64 KB body. Schema: /openapi.json; types: GET /v1/types. - POST /v1/dataset: related tables with foreign keys that agree, in one call (below). - GET /v1/invalid?kind=email|card|address|unicode|iban|ssn|all&count=10&seed=42&fields=value,reason: invalid-on-purpose values, each with the reason it is invalid. Card values are card numbers from Stripe's published testing list or Luhn-invalid numbers. ## Schema in (JSON Schema, OpenAPI) POST /v1/generate also takes "schema" instead of "columns" (send exactly one): a JSON Schema object (draft 2020-12 or draft-07, type object with properties, local $ref into $defs or definitions), or {"openapi": , "component": "Order"} for an OpenAPI component. Pydantic and Zod: send their JSON Schema export. Example: {"rows":5,"seed":42,"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"tags":{"type":"array","items":{"type":"string"}}}}} Formats email, uuid, date and date-time, string enums, integer and number bounds, booleans and the name hints email, first_name, last_name, name and phone map to column types; other strings map to text; nested objects become parent_child columns, one level deep; nullable properties get null_rate 0.1. Anything else (arrays, pattern, other formats, other anyOf/oneOf) is left out, never guessed, and named in X-Schema-Unmapped (comma-separated paths). X-Schema-Version is the mapping version (1). example, examples, default, const and x-* values are never used or echoed. ## Related tables in one call POST /v1/dataset: up to 10 related tables of fake test data in one call, as SQL (default) or JSON. A column {"name":"customer_id","type":"ref","table":"customers","column":"id"} is a foreign key: each value is drawn by seed from the primary key values generated for an earlier table of the same request, so the keys agree. Refs resolve only inside the request; nothing is stored. Tables come in dependency order: a ref to a later table, to its own table, to a table not in the request or to a column that is not the primary key is refused with 400 and a next step. Primary key: a uuid or sequence column named id, or one column marked "primary_key": true. Limits: 500 rows across all tables, and the tables' cost units together within the per-call budget; one call counts once against the daily limit. A table may send "schema" (as on /v1/generate) instead of columns; ref columns need columns. Example: {"seed":42,"format":"sql","tables":[{"name":"customers","rows":3,"columns":[{"name":"id","type":"uuid"},{"name":"email","type":"email"}]},{"name":"orders","rows":5,"columns":[{"name":"id","type":"sequence"},{"name":"customer_id","type":"ref","table":"customers","column":"id"},{"name":"total","type":"decimal","min":5,"max":500}]}]} SQL: per table, CREATE TABLE and then its INSERTs (TEXT, BIGINT, NUMERIC, BOOLEAN, DATE, TIMESTAMPTZ; PRIMARY KEY; FOREIGN KEY). Written in the dialect common to PostgreSQL and SQLite 3.23 or later; CI executes it in SQLite with foreign keys enforced and in PostgreSQL 16. JSON: {"tables":{"customers":[...],"orders":[...]}}. Each table has its own seed, derived from the request seed and the table name, so adding a table leaves the earlier tables' rows unchanged. Headers as on /v1/generate (X-Reproduce on SQL, X-Request-SHA256, X-Output-SHA256), plus X-Dataset-Version (1; pin it with "dataset_version") and X-Dataset-Rows-SHA256: customers=,orders=, per table the sha256 of the JSON of its rows as arrays of values in column order, the same for the SQL and the JSON form of one request. ## Payment sandbox (TEST DATA) A fake card processor for testing payment flows. It moves no money and contacts no payment provider. It accepts only card numbers from Stripe's published testing list; any other card-like number (13-19 digits passing Luhn) in any field is refused and not stored. Every object carries "livemode": false, "sandbox": true and "test_data": "TEST DATA"; every response carries X-Rowsmith-Sandbox: test. A sandbox and everything in it is deleted 1 hour after creation. A sandbox holds at most 200 charges and refunds; each caller may write 3000 storage rows per day across all its sandboxes (a charge is about 4), then gets 429 until 00:00 UTC. - POST /v1/sandbox body {"seed": 42} -> {"id": "sbx_..."} - POST /v1/sandbox/{id}/charges {"amount": 1999, "currency": "usd", "card": "4242424242424242", "reference": "order_1", "metadata": {"cart": "c_9"}} No free text: reference and metadata keys and values are 1-40 of A-Z a-z 0-9 _ -, with fewer than 8 digits in a row; descriptions and other fields are refused. - POST /v1/sandbox/{id}/refunds {"charge": "ch_test_...", "amount": 500} - GET /v1/sandbox/{id}/charges?limit=10&fields=id,status,amount - GET /v1/sandbox/{id}/charges/{charge_id} - POST /v1/sandbox/{id}/reset Declines use the published decline test numbers (GET /v1/invalid?kind=card lists them with their codes) and come back as HTTP 422 with "code" and "decline_code". Same seed and same call sequence give the same objects for sandbox version 2, except the random sandbox id. ## Chaos (testing how an agent handles failures) Query parameters on /v1/generate, /v1/invalid and the sandbox: fail=429@0.2 (also 500, 503), latency=800 (ms, at most 3000), empty_result=1, attempt=N. Outcomes are fixed by seed and attempt (the sandbox uses its own operation counter). Injected failures carry X-Chaos. ## Limits and errors 200 calls per caller per day across all endpoints; 20000 in total. Every response carries X-RateLimit-Limit and X-RateLimit-Reset (seconds to 00:00 UTC); counted calls also carry X-RateLimit-Remaining. Errors are JSON {"error": "...", "next": "..."}: "next" says what to do. ## MCP npx -y rowsmith-mcp (MCP Registry: io.github.meiorz/rowsmith). Tool definitions, measured as characters of the tools/list JSON divided by 4: 3 tools, about 1370 tokens (5477 characters); 5 tools when the service advertises the sandbox in /pricing.json, about 1978 tokens (7910 characters). ## More - https://testdata-api.re-jam620.workers.dev/openapi.json - https://testdata-api.re-jam620.workers.dev/pricing.json - https://testdata-api.re-jam620.workers.dev/status.json (last 7 days, approximate; team traffic excluded) CORS: /v1/* and these docs routes allow any origin, without credentials.