CSV roster sync: getting a nightly export from any SIS into Kastr

This is the universal route. Whatever your student information system is, including the one nobody writes documentation for, if it can produce a CSV on a schedule, it can feed Kastr. No OneRoster required, no vendor relationship required, and no credential of yours held by us.

Last reviewed 2026-08-04

Common roster CSV cases, how the preview handles them, and the next step
FailureWhat actually happenedWhat you seeFix
Truncated exportThe SIS job died halfway; the file is valid CSV with 40% of your studentsRoster API: aborted_guardrail, an HTTP 409, nothing written. Import page: no withdrawals, because missing rows are left unchangedRe-run the export; add a row-count sanity check to the export job
UTF-8 byte-order markExcel or a Windows export prepended a BOM, so the first header reads student_idA UTF-8 BOM is accepted by the roster CSV parserUse UTF-8 and the supplied column headings; a BOM needs no manual removal
Excel-mangled identifiersSomeone opened the file; 0012345 became 12345Roster API: mass adds plus mass withdrawals, usually an abort. Import page: a preview full of new peopleNever round-trip through a spreadsheet; quote the column or export as text
Phone numbers without country code(555) 010-1234 with no +1Preview reports an invalid phone number and cannot be appliedSupply an international number beginning with +; the roster CSV does not assume a country code
Duplicate rowsA join fan-out produced the same student-guardian pair three timesDuplicate source IDs or relationships block the previewFix the join; a fan-out usually means a field you did not intend is in the key

Identifier changes can make a file that looks unchanged to a person appear different to the matcher. If your process involves anyone opening the export to check it, that person needs a copy, not the original.

The cron recipe

For a non-OneRoster export, an administrator can use the typed CSV template on the Import page, or a district-owned job can send mapped JSON entries to the roster API. OneRoster CSV ZIP uploads use Integrations, OneRoster; REST connections can run automatically when the worker is configured.

Assume the export lands at /srv/sis/exports/roster-YYYYMMDD.csv at about 01:30. The added job, at 03:00:

  • Check the file exists and is newer than the last successful run. If not, exit non-zero so your monitoring notices.
  • Turn each row into a roster entry: the identifier as sourceIdExt, the role (student, guardian or teacher), given and family name, and where you have them email, phone, languagePref and, for a guardian, the student's identifier as guardianOfSourceId.
  • POST the entries to /api/v1/roster/sync with a bearer token read from your secret store rather than from a config file, and log the response to the same place your other overnight jobs log.

The Kastr CLI is not that job: its roster command only runs a synthetic demo sync, and only for demo organisations. The CLI and the MCP server are MIT-licensed and open source, sixteen commands and nineteen tools, and the MCP server's run_roster_sync tool can send real entries if you want an agent in the loop. Both run on your host. You can read what they send before you trust them with anything, which is a materially different proposition from a vendor-hosted connector.

Nothing needs installing for the POST itself: it is plain HTTPS with a bearer token, so curl or any HTTP client will do, up to 50,000 entries per call.

Check the custom API batch before posting. The JSON roster API has no dry-run mode. The typed CSV import and the separate native OneRoster REST/CSV paths provide manual previews. Their omission and withdrawal rules differ.

What each route does with a missing row

The two routes treat a row that is missing from the file differently, and it is worth knowing which one you are on before the first sync:

  • Roster API. Each POST is compared with the records Kastr holds for that source. A new identifier is added, a changed record is updated, an identical one is left alone, and anyone missing from the batch is withdrawn. Missing means withdrawn, so a batch that is short by accident is a withdrawal by accident.
  • Import page. The upload shows a preview of the changes and applies nothing until an administrator confirms. Rows left out of the file are left unchanged, and only people whose rows explicitly say withdrawn are withdrawn. A short file cannot withdraw anyone by omission.
  • Unchanged records. On the API route each record payload is hashed with SHA-256 and compared with the stored hash. An identical hash means no write at all, which is why a stable district produces a fast sync.

If an API sync would withdraw more than half of active records, it stops with an HTTP 409, records aborted_guardrail and writes nothing, whatever the cause. Overriding it takes force: true in the request, a deliberate act rather than a retry. That single behaviour is the difference between a bad export and a bad week.

Map your export to the supplied roster template

The roster CSV uses the supplied, case-sensitive column headings. The parser trims surrounding whitespace and accepts a UTF-8 byte-order mark, but it does not guess aliases or map an arbitrary SIS export. Map your export to the template before upload.

Rows have a kind, such as school, class, student, guardian, staff, relationship, enrollment or teaching. Person rows use source_id, given_name, family_name, and optional email and phone. Relationship and enrollment rows refer to those source IDs. Unknown or duplicate headings are rejected.

Keep the source namespace and identifiers stable between imports. CSV identities are derived from the organisation, namespace and source ID. Changing any of those can create a new identity instead of updating the existing person.

Use the example file on the Import page for the required fields for each row kind. The preview checks names, references, duplicate identities, email syntax and international phone format before an administrator can apply it.

Where the API key lives

Kastr API keys are SHA-256 hashed at rest and shown exactly once, at creation. There is no recovery path: if it is not in your secret store, you rotate it. That is deliberate, and it means a support ticket can never contain your key because we do not have it either.

Generic roster-source configs reject literal credentials. Paste a string beginning sk_, whsec_, AKIA, AIza, ghp_, xoxb- or -----BEGIN into a roster connector configuration and the save is refused. You store a reference to a secret in your own store instead. A rejected paste is the feature working: the alternative is a district's SFTP password sitting in a vendor's database, and then in a screenshot, and then in a ticket. The dedicated OneRoster REST connection is separate: its client secret is accepted in the connection form and stored encrypted with AES-256-GCM.

For the cron job itself, read the key from an environment variable populated by your secret manager, or from a file with restrictive permissions owned by the job's user. Do not put it in the crontab, where it appears in process listings. Rotate on staff changes; key creation, use and revocation are all recorded in the append-only audit log.

To answer the most common question directly: Kastr does not host an SFTP server for you to drop files onto. The file stays on infrastructure you control and your job sends its contents as JSON entries. That means one fewer credential in our hands and one fewer place your student data sits at rest waiting to be processed.

Questions people actually ask

Does Kastr host an SFTP server I can drop files onto?

No. The file stays on infrastructure your district controls, and a job you own turns it into JSON entries and POSTs them to the roster endpoint over HTTPS, or an administrator uploads it on the Import page. That is one fewer credential for us to hold and one fewer place your student data sits at rest. If your workflow genuinely requires a drop target, host it yourself and point the POST job at it.

Can I test a sync without changing any live records?

The custom JSON roster API applies a valid POST immediately. Manual OneRoster REST syncs, OneRoster CSV ZIP uploads and the typed CSV import provide a preview before apply. Use the page appropriate to the input format.

What happens if my export is truncated and only half the students are in the file?

Through the roster API, the run computes that accepting it would withdraw more than 50% of active records, refuses with an HTTP 409, records aborted_guardrail and writes nothing. Yesterday's roster stands. Through the Import page, a truncated file withdraws nobody, because rows missing from the file are left unchanged. This is the failure that ends badly elsewhere precisely because a truncated CSV is still valid CSV and every individual step reports success.

Does the CSV have to be in OneRoster format?

No. The Import page uses Kastr’s typed roster CSV template. A OneRoster export is uploaded as a ZIP on Integrations, OneRoster instead; its missing-record and validation rules differ.

How do I stop an API key from ending up in a config file in version control?

Read it from an environment variable populated by your secret manager, or from a permission-restricted file owned by the job's user. Never put it in the crontab, where it shows up in process listings. Roster connector configs also hard-reject credential-shaped strings outright, so the obvious wrong move fails loudly instead of quietly. The dedicated OneRoster REST connection is separate: its client secret is accepted in the connection form and stored encrypted with AES-256-GCM.

One price. Every feature. Locked for three years.

$3.50 per student per year under 5,000 students. No tiers or add-on modules. Normal messaging is included under a published fair-use allowance, with transparent cost recovery only above it.