Build

Data import & export

CSV templates, bulk endpoints, validation rules, and migration playbooks for moving data into - or out of - Lectern.

Overview

Lectern accepts bulk data in two ways: through the Settings → Imports wizard in the dashboard (with column mapping and a live preview), or through the bulk API. Both run the same validators and produce identical results - the wizard is just the API with a UI on top.

CSV templates

Start by downloading the canonical template for each resource. Column order doesn’t matter; column names do. Empty optional columns are fine.

Uploading via the API

Bulk uploads are two-step: first POST the file, then poll for validation results.

cURLPOST /v1/imports
Copy
$ curl https://api.lectern.school/v1/imports \
    -H "Authorization: Bearer sk_live_lectern_…" \
    -F "resource=learners" \
    -F "file=@./learners.csv"
Response202 Accepted
application/json
{
  "id": "imp_5f3c0e2a",
  "status": "validating",
  "resource": "learners",
  "rows": 418,
  "created_at": "2026-05-07T11:42:08Z"
}

Poll GET /v1/imports/{id} until status is ready_for_review. The response includes a per-row validation summary - rows that failed validation won’t be applied until you fix them and re-upload, or override in the dashboard.

Validation rules

Lectern runs three layers of validation before any write:

  • Schema - required columns present, types correct, dates parseable.
  • Reference - foreign keys resolve. guardians.learner_id must match an existing learner; classes.teacher_email must match a staff member.
  • Business rules - no duplicate enrollments per term; class sizes within tier limits; finance rows balance.

Failed rows surface in the import response with a row_errors array containing the row number, the column at fault, and a stable error code:

Response200 OK
application/json
{
  "id": "imp_5f3c0e2a",
  "status": "ready_for_review",
  "rows_total": 418,
  "rows_valid": 412,
  "rows_invalid": 6,
  "row_errors": [
    {
      "row": 42,
      "column": "guardian_email",
      "code": "invalid_email"
    }
  ]
}

Applying the import

Once you’re happy with the preview, apply the import:

cURLPOST /v1/imports/{id}/apply
Copy
$ curl -X POST https://api.lectern.school/v1/imports/imp_5f3c0e2a/apply \
    -H "Authorization: Bearer sk_live_lectern_…"

Apply runs in the background. Lectern fires a import.applied webhook when finished, and every row creates a per-resource webhook (e.g. learner.created) for downstream consumers.

Exports

Every list endpoint accepts ?format=csv for flat downloads, or you can request a full-tenant export from Settings → Exports. Tenant exports are zipped CSVs delivered to a signed URL; the URL is valid for 24 hours.

Limits

  • File size: 25 MB per import (~50,000 rows for most resources)
  • Rate: 5 imports running concurrently per tenant
  • Retention: uploaded files are stored for 7 days after apply, then permanently deleted