Status: DRAFT — open for community review. Lock target: 2026-09-30.
Editor: Stanley (AI2NLP Solutions Inc.) · stanley@openeeg.org
Spec version: 0.1.0
An OpenEEG session is a single continuous recording from a single device, represented as two files sharing a common basename:
| File | Purpose | Format |
|---|---|---|
<basename>.openeeg.json |
Metadata (this spec) | JSON, UTF-8 |
<basename>.openeeg.dat |
Sample data (raw float32) | Binary, little-endian |
The two-file split follows the BIDS pattern: structured metadata is small and human-readable; sample data is large and binary. This keeps the format efficient (no JSON parsing of millions of numbers) and inspectable (you can cat the metadata file and understand the recording).
Why two files and not one (e.g., HDF5)? v0.1 deliberately avoids container formats. JSON + raw float32 is:
json, JavaScript JSON.parse, etc.).append() to the .dat file as you record)<basename> is a free-form identifier chosen by the recorder. Suggested pattern: subj-<id>_sess-<yyyymmdd>-<hhmmss> or any UUID-prefixed form..openeeg.json and .openeeg.dat.Example:
subj-7f3a_sess-20260628-142301.openeeg.json
subj-7f3a_sess-20260628-142301.openeeg.dat
| Field | Type | Description |
|---|---|---|
openeeg_version |
string | MUST be "0.1.0" (semver of this spec) |
session.id |
string (UUID) | Unique session identifier |
session.subject_id |
string | Anonymized subject identifier (free-form, but should be opaque — NOT a real name or email) |
session.started_at |
string (ISO-8601) | Wall-clock time of recording start (UTC recommended) |
session.sampling_rate_hz |
number | Sampling rate in Hz (positive, finite) |
channels |
array | Channel definitions (see §4). MUST be non-empty. |
data |
object | Reference to the binary sample file (see §5) |
| Field | Type | Description |
|---|---|---|
session.ended_at |
string (ISO-8601) | Wall-clock time of recording end |
session.duration_seconds |
number | Computed or recorded duration (use for sanity check) |
session.notes |
string | Free-form text |
device |
object | Device metadata (see §6) |
annotations |
array | Event markers (see §7) |
metadata |
object | Free-form key-value for additional context (subject age group, environment, etc.) |
extensions |
object | Vendor- or use-case-specific extensions (see §8) |
The following are NOT allowed at the top level and MUST appear under their proper subsection if used:
version (use openeeg_version)subject, subject_id (use session.subject_id)rate, sampling_rate (use session.sampling_rate_hz)This rule prevents schema drift and tooling fragility.
channels is an ordered array of channel definitions. Order MUST match the column order in the binary data file (§5).
| Field | Type | Description |
|---|---|---|
index |
integer (≥ 0) | Position in the binary data columns. MUST be unique within channels. SHOULD be sequential from 0. |
label |
string | Human-readable label. SHOULD follow 10-20 system (e.g., “Fp1”, “C3”, “O1”, “TP9”, “AF7”). |
units |
string | Physical units of the samples. MUST be "microvolts" for v0.1 if the source is EEG. Other units MAY appear for non-EEG channels (e.g., accelerometer data) but MUST be a recognized unit string. |
| Field | Type | Description |
|---|---|---|
position |
string | 10-20 system position code (e.g., “Fpz”, “C3”, “O1”). Use "extracellular" or similar for non-scalp channels. |
reference |
string | Reference electrode label (e.g., “A1”, “Fpz”, “linked-ears”). For active电极 systems with on-board reference, use the manufacturer’s documented reference. |
impedance_kohms |
number | Electrode impedance at session start (informational) |
filter |
object | Applied filter chain {highpass_hz, lowpass_hz, notch_hz} |
.openeeg.dat)[n_channels, n_samples] (row-major: all samples for channel 0, then all for channel 1, etc.)filesize_bytes = n_channels * n_samples * 4
If the file size does not match this formula, the file is corrupt or the metadata is wrong. The validator (§10) MUST check this.
The data.sha256 field in the JSON, if present, MUST match the SHA-256 of the binary file. This catches silent corruption.
If present, device SHOULD contain:
| Field | Type | Description |
|---|---|---|
manufacturer |
string | e.g., “Interaxon”, “OpenBCI”, “Olimex” |
model |
string | e.g., “Muse 2”, “Cyton 8-channel”, “EEG-SMT” |
firmware_version |
string | Firmware version string (vendor-defined) |
serial_number |
string | Device serial number (optional; consider privacy implications) |
driver |
string | Driver/library used to record (e.g., “muse-lsl”, “openbci-py”, “openeeg-p2”) |
driver_version |
string | Driver/library version |
Each annotation is a single event marker. The array MAY be empty.
| Field | Type | Required | Description |
|---|---|---|---|
timestamp_seconds |
number | yes | Time of the event, in seconds from session.started_at. May be negative if the annotation is pre-session. |
label |
string | yes | Event type or human-readable description |
duration_seconds |
number | no | Duration of the event (e.g., a 5-second eyes-closed period) |
metadata |
object | no | Additional structured data (e.g., {stimulus_id: "auditory_tone_440hz"}) |
Reserved labels (recommended, not enforced):
session_start, session_endeyes_open, eyes_closedmarker (generic; should include a metadata.label field for the actual type)artifact_blink, artifact_movement, artifact_electrodeImplementations MAY add custom labels but SHOULD prefix them with a namespace (e.g., myapp.custom_event).
The extensions top-level object allows vendors and use-cases to add fields without breaking the core spec. Extensions MUST NOT redefine or override any field in §§3–7.
Example:
{
"extensions": {
"muse2": {
"headband_fit": "good",
"battery_pct_at_start": 87
}
}
}
The spec does not define what fields live under which extension namespace — that’s per-vendor.
These names are reserved for future spec versions and MUST NOT be used as custom field names in v0.1:
preprocessing, pipeline, processed (for processed data in v0.2)events (use annotations in v0.1; v0.2 may deprecate)epochs, trials (for epoched data in v0.2)units_, _si prefixes (reserved for SI conversion metadata)A v0.1 validator MUST check, at minimum:
openeeg_version is supported (currently only "0.1.0")index valuesn_channels * n_samples * 4data.sha256 is present)timestamp_seconds in annotations are within [0, duration_seconds]The reference validator (openeeg validate) will be shipped with the reference implementation.
These are deliberately deferred from v0.1 and tracked for v0.2:
If you have opinions on any of these, open an issue.
This specification is licensed under Apache License 2.0.
Copyright 2026 AI2NLP Solutions Inc.