← All field notes

TinyImport · Practical guide · Reviewed September 7, 2026

Review a CSV before importing it into your app

Add CSV upload, column mapping, and explicit review to your app, then pull validated batches into your own backend with safe retry handling.

A customer’s first CSV often has different column names from your database. An identifier may begin with zero. A decimal may need more precision than a JavaScript number can preserve.

TinyImport provides the upload, mapping, validation, and review flow. Your backend chooses when to collect an approved batch and remains responsible for saving it. Start with synthetic data to verify the complete integration before accepting customer files.

Prefer help from your coding agent? Use the TinyImport setup prompt, then follow this walkthrough for the human review and backend steps.

Validate one synthetic file

Use Bun 1.4 and a new project directory. Follow the agent setup reference to download and checksum-verify the CLI. Create a preview with explicit TinyImport access:

bun ./tinyscale-agent-v0.4.0.mjs workspace create --name "CSV import example" --products tinyimport --cohort external

Create import-schema.json with your owned HTTPS origin:

{
  "name": "Reference numbers",
  "allowedOrigins": ["https://your-owned-app.example"],
  "schema": {
    "version": "tinyimport.schema.v1",
    "revision": 1,
    "fields": [{"key":"reference","type":"text","required":true,"maxLength":80}]
  }
}

Create a synthetic CSV named synthetic.csv:

Reference
00123
00456

Create import-mapping.json:

{"version":"tinyimport.mapping.v1","revision":1,"columns":[{"source":"Reference","target":"reference"}]}

Configure, upload, map, and validate, replacing the IDs with the metadata returned by each command:

bun ./tinyscale-agent-v0.4.0.mjs import configure --config import-schema.json
bun ./tinyscale-agent-v0.4.0.mjs import upload --schema SCHEMA_ID --revision 1 --file synthetic.csv --idempotency-key synthetic-upload-0001
bun ./tinyscale-agent-v0.4.0.mjs import map --import IMPORT_ID --mapping import-mapping.json
bun ./tinyscale-agent-v0.4.0.mjs import validate --import IMPORT_ID
bun ./tinyscale-agent-v0.4.0.mjs import status --import IMPORT_ID

Wait for validation to finish. A ready result with two valid rows proves validation; it does not authorize delivery or prove that your application saved the rows. Keep .tinyscale credentials and customer CSV contents out of source control, logs, and model context. Reuse the upload idempotency key after a transport failure.

Review and claim the project

Run workspace claim and claim the project in the owner console. The same integration continues under Free limits without a card. Open its imports, review the mapping and normalized sample, and explicitly approve delivery. Mapping changes invalidate previous validation and approval.

The agent’s CLI and MCP access expose bounded metadata. They do not approve delivery or retrieve customer rows.

Add the widget to your app

In the owner console, open Connect your app’s import widget. Choose the schema and its exact allowed HTTPS origin. Save the issued backend token in your backend’s secret store. Enable end-user approval only if you want authenticated users of your app to approve their own imports; otherwise the owner reviews delivery in TinyScale.

Your backend authenticates the current app user and exchanges that token for a short-lived browser session. Return only the session response with Cache-Control: no-store. The TinyImport integration reference contains the endpoint and versioned request contracts.

Load the widget after implementing your authenticated /api/import-session route:

<div id="csv-import"></div>
<script src="https://api.tinyscale.io/downloads/tinyimport-widget-v0.1.0.js" defer></script>
<script src="/import.js" defer></script>

In your own /import.js:

const response = await fetch('/api/import-session', { method: 'POST' });
if (!response.ok) throw new Error('Import session unavailable');
const session = await response.json();
const widget = TinyImport.mount({
  host: document.getElementById('csv-import'),
  workspaceId: session.workspaceId,
  schemaId: session.schemaId,
  sessionToken: session.sessionToken,
  ingestOrigin: 'https://ingest.tinyscale.io',
  maxBytes: 1048576
});

Apply your app’s CSRF policy to the session route and the reference’s CSP settings to the page. Set maxBytes from the current allowance. Call widget.destroy() when removing the view. Neither your backend token nor customer rows belong in analytics callbacks.

Save a batch once, then acknowledge it

After approval, your backend pulls batches using the schema-scoped token. Each batch has a stable batch ID and stable row IDs. In one database transaction, deduplicate the identity, apply the complete batch, and record acceptance. Commit before acknowledging TinyImport.

If your process stops after commit but before acknowledgment, retry with the stored acceptance. Do not apply the same batch again. TinyImport accepts duplicate complete acknowledgments and rejects changed or incomplete ones. This protocol supports safe retries; it does not make an arbitrary customer database write exactly once.

Verify this with synthetic data: pull the same batch twice, confirm identical IDs, save it once, acknowledge twice, and check both your stored row count and TinyImport’s completed status.

Limits and supported files

Preview lasts 72 hours and permits two schemas, three imports, and up to 1 MiB or 1,000 rows per file. Free permits five schemas, ten imports per UTC month, and up to 5 MiB or 10,000 rows per file. Limits hard-stop without automatic overage.

Version 1 supports UTF-8 CSV, quoted fields and newlines, text, integers, exact decimals, explicit date formats, and literal true/false values. It does not support XLSX, archives, arbitrary JavaScript transformations, or locale guessing. Numeric output stays textual to preserve precision.

Raw files, previews, and errors expire after seven days, or earlier when the preview expires. Your database remains authoritative, and already accepted writes are not undone by cancellation or erasure. Keep import failures separate from your app’s core workflow.