Migrate from SessionBoard
Move your event's program — tracks, speakers, sessions, schedule — to your own OpenSession instance in about five minutes. One CLI, both public APIs, safe to re-run.
Why migrate
- Own your data. Everything lives in your Postgres database, exportable at any time — no vendor lock-in, no per-seat pricing.
- Same API shape. OpenSession's REST API mirrors SessionBoard's conventions — x-access-token auth, snake_case payloads, POST …/create — so existing scripts and integrations port with a URL change.
- The whole program workflow. CFP forms with conditional logic, evaluation rounds, one-click accept/decline emails, a speaker portal, and a drag-and-drop agenda with live conflict detection.
- Open source, MIT licensed. Self-host on a free tier; read, fork, and extend every line.
What moves over
| Entity | Deduped by | Notes |
|---|---|---|
| Tracks, tags, formats, levels, rooms | name | Created if missing; track colors snap to the nearest palette preset, room capacity carries over. |
| Custom field values | internal_name | SessionBoard's array shape becomes an object keyed by internal_name. Definitions are reported, not created — add them in Settings → Fields to surface the values. |
| Contacts | Profile, company, links, pronouns, bio, custom fields. Contacts without an email are reported and skipped. | |
| Sessions | client_session_id, then title | Title, description (HTML kept), status, abstract flag, schedule, capacity, CEU credits, metadata by name, tags, custom fields. The SessionBoard id is stored as client_session_id. |
| Participants | contact email | Speakers, moderators and chairpersons (legacy and Sessions 2.0 shapes) attach with their role and primary flag via a follow-up update. |
Not migrated: evaluations, sent emails, speaker-portal state, uploaded files, agenda drafts, and subsessions — see the FAQ below.
The 5-minute migration
- 1
Grab your SessionBoard API token
In SessionBoard: Organization Settings → API Tokens. Read scopes are enough — the migration never writes to the source. Note your event id (an integer), then sanity check the token:
curl -H "x-access-token: $SB_TOKEN" \ https://public-api.sessionboard.com/v1/events - 2
Mint an OpenSession token
In your OpenSession event: Settings → API Tokens. Include the read and write scopes for metadata, contacts, and sessions. Then find your destination event's UUID:
curl -H "x-access-token: $OS_TOKEN" \ https://your-opensession-host/api/v1/events - 3
Dry-run the migration
From a checkout of the OpenSession repo (after
pnpm install). The dry run prints the full plan — per-entity counts and samples — without writing anything:pnpm exec tsx scripts/migrate-from-sessionboard.ts \ --source-url https://public-api.sessionboard.com \ --source-token $SB_TOKEN --source-event 12345 \ --dest-url https://your-opensession-host \ --dest-token $OS_TOKEN --dest-event $EVENT_UUID \ --dry-run - 4
Run it for real, then verify
Drop
--dry-runand run the same command. You'll get a created/skipped/failed table plus per-row failure detail. Re-running is safe — a second pass skips everything. Then spot-check:curl -H "x-access-token: $OS_TOKEN" \ "https://your-opensession-host/api/v1/event/$EVENT_UUID/sessions?pageSize=5"Or open your event in the app: Submissions, Speakers, and Agenda should all be populated.
Do it with an AI agent
The migration is a CLI plus two documented REST APIs — exactly the kind of task coding agents are good at. Paste one of these into Claude Code (or any agent with shell access), fill in the placeholders, and keep your tokens in environment variables.
Clone https://github.com/ky-zo/opensession and run a SessionBoard migration for me.
Source: SessionBoard event <EVENT_ID>, token in $SB_TOKEN.
Destination: https://<my-opensession-host>, event <EVENT_UUID>, token in $OS_TOKEN.
1. Run scripts/migrate-from-sessionboard.ts with --dry-run and show me the plan.
2. Stop and wait for my approval.
3. After I approve, run it for real, then run it once more to confirm the
second pass skips everything (it is idempotent).
4. Summarize created/skipped/failed per entity and any warnings I should act on.
Never paste my tokens into the chat and never commit them anywhere.OpenSession also exposes an MCP endpoint at /api/mcp, so agents can explore your destination event with structured tools instead of raw curl. Configure it with your API token:
{
"mcpServers": {
"opensession": {
"url": "https://your-opensession-host/api/mcp",
"headers": { "x-access-token": "<your OpenSession API token>" }
}
}
}Using the opensession MCP server, list my events, then look at event
<EVENT_UUID>: which tracks, tags, formats, levels, rooms, contacts and
sessions already exist? Summarize what a SessionBoard import would still
need to add, and flag anything that looks like a duplicate.My SessionBoard event <EVENT_ID> was just migrated to OpenSession event
<EVENT_UUID>. Compare the two over their REST APIs (both use an
x-access-token header; SessionBoard is at public-api.sessionboard.com/v1,
OpenSession at https://<my-opensession-host>/api/v1): count tracks, tags,
formats, levels, rooms, contacts and sessions on each side, diff the
session titles, and flag any session whose track, room, schedule or
speaker list does not match the source.Field mapping reference
OpenSession's API is SessionBoard-shaped by design, so most fields map 1:1 under the same name. The full serialization lives in packages/api/src/rest/serialize.ts.
Sessions
| SessionBoard | OpenSession | Notes |
|---|---|---|
| id | client_session_id | kept for traceability; a new UUID is assigned |
| friendly_id | friendly_id | re-allocated as SESS-n in order of import |
| title / description | title / description | HTML descriptions preserved |
| status | status | same enum (accepted, accept_queue, pending, …) |
| is_abstract / is_public | is_abstract / is_public | |
| starts_at / ends_at | starts_at / ends_at | |
| capacity / ceu_credits | capacity / ceu_credits | |
| track / format / level / room | track_id / format_id / level_id / room_id | matched by name |
| tags[] | tag_ids[] | matched by name |
| custom_fields (array) | custom_fields (object) | keyed by internal_name |
| participants / speakers / moderators / chairpersons | participants[] | role + is_primary kept |
| language, subsessions, composition, files | — | not migrated |
Contacts
| SessionBoard | OpenSession | Notes |
|---|---|---|
| first_name / last_name / email | first_name / last_name / email | email is required and deduped |
| company_name / title | company_name / title | |
| about | about | |
| phone_mobile (or phone_home) | phone_mobile | |
| photo_url / website_url / linkedin_url / twitter_url / facebook_url | same names | |
| pronouns | pronouns | |
| custom_fields (array) | custom_fields (object) | keyed by internal_name |
| speaker-bureau extras (speaker_score, availability, industry, …) | — | not migrated |