# Adapter Implementation
Source: https://docs.elata.bio/adapter-implementation
Build a device adapter that plugs into BleTransport and emits stable headband frames.
Once your protocol inventory is complete, build the adapter.
The goal is to isolate device-specific BLE and packet logic while preserving the
shared Elata transport contract for apps.
## Recommended Shape
For browser BLE integrations, the adapter should satisfy the `BleDeviceLike`
shape expected by `@elata-biosciences/eeg-web-ble`.
That means the device layer handles discovery, session setup, subscription, and
packet decoding, while `BleTransport` handles the app-facing transport surface.
## Implementation Order
Define the `requestDevice` filters and any optional services required to find
the device cleanly in the browser picker.
Connect to GATT, resolve characteristics, and do any startup writes that are
required before streaming can begin.
Turn vendor packets into `number[][]` where each row is one time step and
the row width matches `numEegChannels`.
Expose `samplingRate`, `eegNames`, `numEegChannels`, and related device
metadata accurately.
Unsubscribe, stop the stream safely, and release the session without leaking
resources.
## Minimal Adapter Skeleton
```ts theme={null}
import type { BleDeviceLike } from "@elata-biosciences/eeg-web-ble";
export class VendorBleDevice implements BleDeviceLike {
isAthena = false;
samplingRate = 250;
eegNames = ["CH1", "CH2"];
numEegChannels = 2;
opticsChannelCount = 0;
getBoardInfo() {
return { device_name: "VENDOR_DEVICE" };
}
getCharacteristicInfo() {
return { characteristics: [] };
}
async prepareSession() {
// Connect to GATT, resolve characteristics, and initialize streaming state
}
async releaseSession() {
// Tear down subscriptions and release the browser session
}
async startStream(eegCb, _ppgCb) {
// Subscribe to notifications and emit decoded EEG rows
eegCb([[0, 0]]);
}
async stopStream() {
// Stop device streaming and unsubscribe
}
}
```
## What the Adapter Must Own
| Responsibility | Adapter owns it? |
| - | - |
| Device discovery | yes |
| GATT connection and characteristic setup | yes |
| Packet decoding | yes |
| Accurate metadata | yes |
| App-facing transport lifecycle | usually through `BleTransport` |
| Normalized frame delivery to apps | through the shared transport contract |
## Packet Decoding Rules
Keep the decoder boring and deterministic.
| Rule | Why it matters |
| - | - |
| Reject malformed packets early | Prevents bad rows from reaching apps |
| Preserve channel order | Keeps downstream features and models consistent |
| Batch rows only when needed | Avoids hidden latency and timestamp confusion |
| Track packet counters when available | Helps detect drops or corruption |
| Separate EEG from aux logic cleanly | Makes debugging and testing easier |
## Session Lifecycle Rules
Your adapter should make these transitions unsurprising:
| Stage | Good behavior |
| - | - |
| Discover | only matching devices are shown |
| Connect | required services and characteristics resolve clearly |
| Start | stream begins once, without duplicate subscriptions |
| Stop | notifications stop cleanly |
| Release | the session can be started again later |
## Reliability Notes
`startStreaming()` is the safest default for app consumers because it combines
connect and start. Your adapter should still make the underlying `connect()`
and `start()` steps reliable on their own.
If the device exposes auxiliary signals such as PPG, optics, IMU, or battery,
map them only when the semantics are clear. Do not force Muse-specific
assumptions onto non-Muse hardware.
## Related Docs
* [EEG BLE Getting Started](/sdk/eeg-web-ble/getting-started)
* [Transport Contract](./transport-contract)
* [Testing and Validation](./testing-and-validation)
* [Muse Device](/sdk/eeg-web-ble/muse-device)
## Next
Turn the adapter into a reliable transport under real failure conditions.
Package the integration with the docs and caveats other teams need.
# Claude Code
Source: https://docs.elata.bio/apps/ai-tools/claude-code
Use Claude Code to build Elata apps with the Elata SDK and Elata APIs.
## Overview
Claude Code works best on Elata apps when it knows how Elata works: the
sandbox, the SDK packages, and the upload rules. This page gives you a project
context file to paste in, plus prompts for common tasks.
***
## Setup
1. Install Claude Code by following the [Claude Code docs](https://docs.claude.com/en/docs/claude-code/overview).
2. Open a terminal in your project and run `claude`.
3. Add a `CLAUDE.md` file at your project root with the context below.
***
## Project context
Save this as `CLAUDE.md` at your project root:
```markdown CLAUDE.md theme={null}
# Elata app
This is a browser app for Elata (https://app.elata.bio), built on
the Elata SDK. It is a static web build that runs inside a sandboxed
iframe in Elata.
## Docs
- Docs home: https://docs.elata.bio (full text for agents: https://docs.elata.bio/llms-full.txt)
- Building apps: https://docs.elata.bio/apps/build/overview
- Host bridge (Elata APIs): https://docs.elata.bio/apps/platform/host-bridge
- SDK overview: https://docs.elata.bio/sdk/overview
- Package references for agents: node_modules/@elata-biosciences//llms.txt
## Packages
- @elata-biosciences/rppg-web: camera heart rate (use createRppgSession)
- @elata-biosciences/eeg-web + eeg-web-ble: EEG over Web Bluetooth (Muse built in)
- @elata-biosciences/ppg-web: headband PPG heart rate and HRV
- @elata-biosciences/biosignal-session / biosignal-analytics: recording and analysis (not yet on npm)
- @elata-biosciences/app-payments: in-app purchases (Elata checkout)
- @elata-biosciences/app-state: per-user key-value storage (not yet on npm; use the elata:state:* messages)
- @elata-biosciences/app-metrics: event records, scores, reportAffect
## Elata rules
- The build must actually use an Elata sensor SDK (rPPG, EEG, or PPG), or upload fails.
- Ship a zip with index.html at the root; use relative asset URLs.
- Never handle wallets, payments, or sessions in the app. Use app-payments for purchases.
- Never send raw camera frames, audio, or raw biosignals off the device.
- Never access window.parent beyond postMessage.
- Put the release version in public/elata.json: { "version": "x.y.z" }.
## Commands
- npm run dev start the dev server
- npm run build:zip build and write app.zip for upload
```
***
## Useful prompts
### Start a new app
```text theme={null}
Scaffold a new camera heart-rate app with
npm create @elata-biosciences/elata-demo my-app -- --template rppg
and explain the project structure.
```
### Add the SDK to an existing app
```text theme={null}
Add camera heart rate with @elata-biosciences/rppg-web. Use createRppgSession,
show signal quality, and stop the camera when the component unmounts.
Read node_modules/@elata-biosciences/rppg-web/llms.txt first.
```
### Add a purchase
```text theme={null}
Add a "Pro theme" unlock with @elata-biosciences/app-payments. Gate the UI on
getOwnedItems(), branch on result.status (not txHash), and re-apply owned
benefits on startup.
```
### Save progress
```text theme={null}
Persist the player's settings and best score with Elata's elata:state:get/set messages.
Fall back to localStorage when Elata replies with not_authenticated.
```
### Prepare an upload
```text theme={null}
Check that this build is ready for Elata: index.html at the zip
root, relative asset paths, SDK usage present, and public/elata.json with the
version from package.json. Then run npm run build:zip.
```
***
## Tips
Point the assistant at a package's `llms.txt` in `node_modules` before asking
it to write integration code. These files are written for AI tools and list
the supported APIs and common mistakes.
* Start from a `create-elata-demo` template, then iterate.
* Test in Elata's play window before submitting. The sandbox behaves differently from `localhost`.
***
## Next
How publishing works
The Elata SDK packages
# Cursor
Source: https://docs.elata.bio/apps/ai-tools/cursor
Use Cursor to build Elata apps with the Elata SDK and Elata APIs.
## Overview
Cursor works best on Elata apps when it knows how Elata works: the
sandbox, the SDK packages, and the upload rules. This page gives you a project
context file to paste in, plus prompts for common tasks.
***
## Setup
1. Install Cursor from [cursor.com](https://cursor.com).
2. Open your project.
3. Add a project rule at `.cursor/rules/elata.mdc` with the context below.
***
## Project context
Save this as a project rule at `.cursor/rules/elata.mdc`:
```markdown .cursor/rules/elata.mdc theme={null}
# Elata app
This is a browser app for Elata (https://app.elata.bio), built on
the Elata SDK. It is a static web build that runs inside a sandboxed
iframe in Elata.
## Docs
- Docs home: https://docs.elata.bio (full text for agents: https://docs.elata.bio/llms-full.txt)
- Building apps: https://docs.elata.bio/apps/build/overview
- Host bridge (Elata APIs): https://docs.elata.bio/apps/platform/host-bridge
- SDK overview: https://docs.elata.bio/sdk/overview
- Package references for agents: node_modules/@elata-biosciences//llms.txt
## Packages
- @elata-biosciences/rppg-web: camera heart rate (use createRppgSession)
- @elata-biosciences/eeg-web + eeg-web-ble: EEG over Web Bluetooth (Muse built in)
- @elata-biosciences/ppg-web: headband PPG heart rate and HRV
- @elata-biosciences/biosignal-session / biosignal-analytics: recording and analysis (not yet on npm)
- @elata-biosciences/app-payments: in-app purchases (Elata checkout)
- @elata-biosciences/app-state: per-user key-value storage (not yet on npm; use the elata:state:* messages)
- @elata-biosciences/app-metrics: event records, scores, reportAffect
## Elata rules
- The build must actually use an Elata sensor SDK (rPPG, EEG, or PPG), or upload fails.
- Ship a zip with index.html at the root; use relative asset URLs.
- Never handle wallets, payments, or sessions in the app. Use app-payments for purchases.
- Never send raw camera frames, audio, or raw biosignals off the device.
- Never access window.parent beyond postMessage.
- Put the release version in public/elata.json: { "version": "x.y.z" }.
## Commands
- npm run dev start the dev server
- npm run build:zip build and write app.zip for upload
```
***
## Useful prompts
### Start a new app
```text theme={null}
Scaffold a new camera heart-rate app with
npm create @elata-biosciences/elata-demo my-app -- --template rppg
and explain the project structure.
```
### Add the SDK to an existing app
```text theme={null}
Add camera heart rate with @elata-biosciences/rppg-web. Use createRppgSession,
show signal quality, and stop the camera when the component unmounts.
Read node_modules/@elata-biosciences/rppg-web/llms.txt first.
```
### Add a purchase
```text theme={null}
Add a "Pro theme" unlock with @elata-biosciences/app-payments. Gate the UI on
getOwnedItems(), branch on result.status (not txHash), and re-apply owned
benefits on startup.
```
### Save progress
```text theme={null}
Persist the player's settings and best score with Elata's elata:state:get/set messages.
Fall back to localStorage when Elata replies with not_authenticated.
```
### Prepare an upload
```text theme={null}
Check that this build is ready for Elata: index.html at the zip
root, relative asset paths, SDK usage present, and public/elata.json with the
version from package.json. Then run npm run build:zip.
```
***
## Tips
Point the assistant at a package's `llms.txt` in `node_modules` before asking
it to write integration code. These files are written for AI tools and list
the supported APIs and common mistakes.
* Start from a `create-elata-demo` template, then iterate.
* Test in Elata's play window before submitting. The sandbox behaves differently from `localhost`.
***
## Next
How publishing works
The Elata SDK packages
# Windsurf
Source: https://docs.elata.bio/apps/ai-tools/windsurf
Use Windsurf to build Elata apps with the Elata SDK and Elata APIs.
## Overview
Windsurf works best on Elata apps when it knows how Elata works: the
sandbox, the SDK packages, and the upload rules. This page gives you a project
context file to paste in, plus prompts for common tasks.
***
## Setup
1. Install Windsurf from [windsurf.com](https://windsurf.com).
2. Open your project.
3. Add a `.windsurfrules` file at your project root with the context below.
***
## Project context
Save this as `.windsurfrules` at your project root:
```markdown .windsurfrules theme={null}
# Elata app
This is a browser app for Elata (https://app.elata.bio), built on
the Elata SDK. It is a static web build that runs inside a sandboxed
iframe in Elata.
## Docs
- Docs home: https://docs.elata.bio (full text for agents: https://docs.elata.bio/llms-full.txt)
- Building apps: https://docs.elata.bio/apps/build/overview
- Host bridge (Elata APIs): https://docs.elata.bio/apps/platform/host-bridge
- SDK overview: https://docs.elata.bio/sdk/overview
- Package references for agents: node_modules/@elata-biosciences//llms.txt
## Packages
- @elata-biosciences/rppg-web: camera heart rate (use createRppgSession)
- @elata-biosciences/eeg-web + eeg-web-ble: EEG over Web Bluetooth (Muse built in)
- @elata-biosciences/ppg-web: headband PPG heart rate and HRV
- @elata-biosciences/biosignal-session / biosignal-analytics: recording and analysis (not yet on npm)
- @elata-biosciences/app-payments: in-app purchases (Elata checkout)
- @elata-biosciences/app-state: per-user key-value storage (not yet on npm; use the elata:state:* messages)
- @elata-biosciences/app-metrics: event records, scores, reportAffect
## Elata rules
- The build must actually use an Elata sensor SDK (rPPG, EEG, or PPG), or upload fails.
- Ship a zip with index.html at the root; use relative asset URLs.
- Never handle wallets, payments, or sessions in the app. Use app-payments for purchases.
- Never send raw camera frames, audio, or raw biosignals off the device.
- Never access window.parent beyond postMessage.
- Put the release version in public/elata.json: { "version": "x.y.z" }.
## Commands
- npm run dev start the dev server
- npm run build:zip build and write app.zip for upload
```
***
## Useful prompts
### Start a new app
```text theme={null}
Scaffold a new camera heart-rate app with
npm create @elata-biosciences/elata-demo my-app -- --template rppg
and explain the project structure.
```
### Add the SDK to an existing app
```text theme={null}
Add camera heart rate with @elata-biosciences/rppg-web. Use createRppgSession,
show signal quality, and stop the camera when the component unmounts.
Read node_modules/@elata-biosciences/rppg-web/llms.txt first.
```
### Add a purchase
```text theme={null}
Add a "Pro theme" unlock with @elata-biosciences/app-payments. Gate the UI on
getOwnedItems(), branch on result.status (not txHash), and re-apply owned
benefits on startup.
```
### Save progress
```text theme={null}
Persist the player's settings and best score with Elata's elata:state:get/set messages.
Fall back to localStorage when Elata replies with not_authenticated.
```
### Prepare an upload
```text theme={null}
Check that this build is ready for Elata: index.html at the zip
root, relative asset paths, SDK usage present, and public/elata.json with the
version from package.json. Then run npm run build:zip.
```
***
## Tips
Point the assistant at a package's `llms.txt` in `node_modules` before asking
it to write integration code. These files are written for AI tools and list
the supported APIs and common mistakes.
* Start from a `create-elata-demo` template, then iterate.
* Test in Elata's play window before submitting. The sandbox behaves differently from `localhost`.
***
## Next
How publishing works
The Elata SDK packages
# App Lifecycle
Source: https://docs.elata.bio/apps/app-lifecycle
How an app goes from upload to a live, publicly playable listing.
## Overview
Every app version you publish is a **release**. A release moves through one
pipeline: you upload it, submit it, Elata scans it, and a moderator
approves it.
```
DRAFT ──submit──▶ SUBMITTED ──scan + review──▶ APPROVED (live)
│
└──▶ REJECTED (fix and resubmit)
```
***
## Step 1: Upload
Upload a `.zip` (or a single `.html` file) from the wizard or your app's edit
page. Elata checks it straight away:
* the file type and size are within [limits](/apps/build/upload-and-hosting#limits)
* the bundle has an `index.html` entry and safe file paths
* the bundle uses an Elata sensor SDK
If something is missing you get a clear "App requirements not met" message
listing what to fix. A successful upload becomes a **draft** release, and its
version is read from your [`elata.json`](/apps/build/elata-json).
You can try a draft in Elata's play window, and share it with up to 10
playtest friends, before anyone else can see it.
***
## Step 2: Submit for review
When you submit, Elata checks that your listing is complete. You need an
**app icon** and a **header image**. Screenshots are optional but recommended.
The listing details are frozen onto the release at this point, so later edits
don't change what was reviewed.
You can withdraw a submission while it is pending.
***
## Step 3: Automated security scan
Every submitted release is scanned automatically. The scan looks for things
like wallet-draining code, attempts to escape the sandbox, credential access,
obfuscated code, and missing SDK usage. A release that fails the scan is held
back from Elata.
Scan findings are reviewed by the Elata team and are not shown in detail to
uploaders. If your release is held, you'll hear from us through Elata.
***
## Step 4: Moderator approval
An Elata moderator reviews the release against the
[submission policy](/apps/build/review-and-policy). Once approved, it becomes
your app's live release and you're notified in Elata and by email.
If it's rejected, fix the issues and submit again.
***
## After you're live
Approval makes a release eligible to be played. Whether people can find and
play it also depends on:
| Setting | Who controls it | Values |
| - | - | - |
| **Visibility** | You | Public, Unlisted, Private |
| **Moderation** | Elata | Active, Suspended, Banned |
| **Discovery** | Elata | How prominently the app is ranked. New apps start with limited exposure. |
To ship an update, upload a new build. It goes through the same pipeline, and
your current live release stays up until the new one is approved.
***
## Next
What every upload needs
The wizard, step by step
What reviewers look for
# App Listing
Source: https://docs.elata.bio/apps/build/app-metadata
The fields and images that make up your app's page on Elata.
## Listing fields
You fill these in during the wizard's Publish step and can change them any time
from your app's **Edit** page.
| Field | Required to submit | Limits |
| - | - | - |
| Name | Yes | Up to 50 characters. Set when you register. |
| App icon | Yes | Square image |
| Header image | Yes | Wide banner shown at the top of your app page |
| Description | Recommended | 10 to 1,000 characters. Shown on app cards. |
| Extended description | No | Up to 5,000 characters. Shown on your app page. |
| Category | Yes | Games, Neurometrics, Wellness, Tools, or Experiments |
| Tags | No | Up to 5 tags, each up to 30 characters |
| Screenshots | No | Up to 10 images |
| Social links | No | Website, X/Twitter, Discord, GitHub, Telegram, Farcaster, YouTube, and more, plus up to 5 custom links |
| Team | No | Team members shown on your app page |
When you submit a release, the listing is frozen onto that release. Edits
after that apply to your next release.
***
## Image guidelines
| Asset | Recommended size | Formats |
| - | - | - |
| App icon | 512×512 px | PNG, JPG, WebP |
| Header image | 1920×600 px | PNG, JPG, WebP |
| Desktop screenshot | 1920×1080 px (16:9) | PNG, JPG, WebP |
| Mobile screenshot | 1080×1920 px (9:16) | PNG, JPG, WebP |
Image uploads are limited to 5 MB each.
Your icon is also used for home-screen installs when your bundle doesn't
ship its own web manifest. Use a square icon that reads well at small sizes.
***
## Your app's URLs
| URL | What it is |
| - | - |
| `app.elata.bio/app/` | Your app page |
| `app.elata.bio/app//play` | Where users play your app |
| `app.elata.bio/app/.md` | A plain-text Markdown version of your listing, useful for sharing with AI tools |
***
## Next
What reviewers check
What to do once you're live
# elata.json
Source: https://docs.elata.bio/apps/build/elata-json
The app manifest that declares your release version.
## Overview
`elata.json` is a small manifest you include in your upload. Today Elata
uses it to read your **release version**, so your listing shows `v1.2.3`
instead of `Release #4`.
```json elata.json theme={null}
{
"version": "1.2.3",
"entry": "index.html",
"name": "Calm Runner"
}
```
`elata.json` is optional today. If it is missing or invalid, Elata falls
back to other version sources (see below). It will become required in a
future update, so add it now.
***
## Fields
| Field | Required | Rules |
| - | - | - |
| `version` | Yes | 1 to 32 characters. Starts with a letter or digit, then letters, digits, `.`, `-`, or `+`. Semver like `1.2.3` or `1.2.3-beta.1` is recommended. |
| `entry` | No | The entry HTML file, up to 256 characters. Use `index.html`. |
| `name` | No | A display name, 1 to 80 characters. |
***
## File name and location
* Name the file `elata.json` (`elata.manifest.json` also works).
* Put it at the root of your zip, next to `index.html`.
* If there are several, the one closest to the root wins.
With Vite, put it in `public/` so it's copied into `dist/` on every build:
```
my-app/
├── public/
│ └── elata.json
├── src/
└── index.html
```
***
## Version fallbacks
If there's no valid `elata.json`, Elata looks for a version in this order:
1. `elata.json` (or `elata.manifest.json`)
2. The `version` in the shallowest `package.json` in the zip
3. A meta tag in `index.html`:
```html theme={null}
```
The upload response tells you which source was used.
# Publish Your App
Source: https://docs.elata.bio/apps/build/launch-your-app
Step-by-step through the Build an App wizard, from registration to submitting for review.
## Overview
Publishing happens in the **Build an App** wizard at
[app.elata.bio/build-app](https://app.elata.bio/build-app). It has four steps.
Your progress is saved as a draft, so you can leave and come back.
```
Build → Register → Upload → Publish
```
***
## 1. Build
The first step is a checklist. Before you continue you should have:
* an app built with an Elata SDK and tested locally
* a production `.zip` ready to upload
If you scaffolded with `create-elata-demo`, run:
```bash theme={null}
npm run build:zip
```
This builds the app and writes `app.zip` with `index.html` at its root, which is
the format Elata expects.
***
## 2. Register
Pick your app's name (up to 50 characters). Elata checks that the name is
available and gives your app a short URL slug, used in
`app.elata.bio/app/`.
Then pay the one-time registration fee in USDC. The amount is shown in the
wizard.
***
## 3. Upload
Upload your `app.zip`. Elata validates it immediately: file type, size,
an `index.html` entry, safe paths, and SDK usage. If anything is missing you'll
see exactly what to fix.
Once it passes, try your app in the play window. This is how users will see it.
Add an [`elata.json`](/apps/build/elata-json) to your build so the release
shows your version number.
***
## 4. Publish
Fill in your Elata listing:
* **App icon** (required)
* **Header image** (required)
* **Description**, category, and tags
* **Screenshots** (optional, up to 10)
Then click **Submit for Review**. Your release goes through the automated
security scan and moderator review described in [App Lifecycle](/apps/app-lifecycle).
***
## After submitting
* Track the review status on your app page and in Elata's notifications.
* Invite up to 10 playtest friends to try the draft before it's approved.
* Ship updates later from your app's **Edit** page. Each new upload creates a new release.
***
## Next
Fields and image guidelines
Limits, self-hosting, PWA install
What to do once you're live
# Monetize Your App
Source: https://docs.elata.bio/apps/build/monetize
Configure products and subscriptions, track sales, and access your proceeds.
Manage your app's monetization from **My Apps → your app → Edit → Commerce**. This section contains products, the monthly subscription plan, revenue, recent transactions, and payout-wallet funds.
Payments must be enabled and configured in the deployment. See [What's live](/overview/status). The app checkout and Funds flow described here uses USDC on Base. Verify the checkout network before making a test purchase.
## Choose what to sell
| Model | Suitable for | What you build |
| - | - | - |
| [In-app purchases](/apps/platform/in-app-purchases) | One-time feature, level, or cosmetic unlocks | Create products, then integrate `app-payments` and apply the purchased benefit |
| [App subscription](/apps/platform/subscriptions) | Monthly access to the whole app | Configure a monthly plan; Elata handles the access gate |
You can offer both. An app subscription provides app access, while item ownership remains separate. A single item identifier does not support repeated consumable purchases.
## Set up monetization
Open **Commerce → Funds** and inspect the payout wallet. Direct app payments use the app creator's wallet as their destination. Confirm you control it before offering paid content. Adding team members does not create payout shares.
Under **Products**, create a title, description, price, optional image, and active state. Elata assigns the stable `contentId` your app uses. Under **Monthly subscription**, set a positive monthly USDC price, trial length, and active state. Each section has its own save action.
For items, read the catalog and ownership through `app-payments`, request checkout, and apply the benefit after success. Restore owned benefits when the app loads. See [In-app purchases](/apps/platform/in-app-purchases) for the integration.
Use a separate user account to test a purchase or subscription. Owners and team members bypass the app subscription gate. Test signed-out, wallet connection, cancellation, owned-item, trial, paid-period, and expired-access states as applicable.
Review **Revenue** and **Transactions**, then use **Funds → Manage funds** to access the payout wallet. Follow [Revenue and payouts](/apps/operate/revenue-and-payouts) to send USDC or move it to a service with bank withdrawals.
## How money reaches you
The buyer pays USDC through the splitter. The splitter deducts the configured platform fee and routes your share directly to the payout wallet. Elata does not keep a separate creator balance that you later claim.
The Funds card displays the wallet's current USDC balance on Base. It can include other deposits and proceeds from other apps using that wallet, and it decreases when you send funds out. It is not an app-specific earnings total.
## Fees and reporting
Registration has a one-time fee shown in the registration wizard. Direct app sales have a configured platform fee deducted by the splitter. Network and service fees may also apply. This guide does not fix a percentage: use the active payment configuration and transaction records.
Commerce reports show **gross** amounts before the split. Product revenue is estimated using current product prices; subscription payments retain their recorded payment amounts. Do not use the gross estimate as your net payout balance.
## Credits and points
User credits cover supported metered features; they are not your app's payout balance. Points are also separate from USDC revenue.
## Next steps
Create items and connect checkout to app benefits.Configure monthly app access and trials.Read your reports and get your money out.Verify the published customer experience.
# Builder Overview
Source: https://docs.elata.bio/apps/build/overview
What Elata gives you and what you avoid building yourself.
The repository's `create-elata-demo` 0.12.1 has a packaging blocker: missing `app-metrics` version metadata prevents startup. Check [scaffolder status](/sdk/create-elata-demo) before using the commands below; for now, follow [manual rPPG integration](/sdk/tutorials/rppg-existing-app).
## What You Get
You ship a static web app. Elata provides the rest:
* Hosting for your build, or embedding of your own HTTPS site
* an Elata listing with discovery, reviews, comments, votes, and sharing
* Signed-in users, with no auth code in your app
* One-time in-app purchases and monthly subscriptions, paid in USDC
* Per-user key-value storage, event records, and score leaderboards
* A consent-gated, cross-app biometric Score
* Foreground reminders and an in-app feedback channel
***
## What You Avoid Building Yourself
| You would otherwise need | Elata handles it |
| - | - |
| Auth, sessions, wallets | Users sign in to Elata. Your app does not receive their account credentials or session tokens through the bridge. |
| Checkout and payment processing | Elata runs checkout and records ownership. You read entitlements. |
| A backend for saves and settings | [`app-state`](/apps/platform/app-state) and [`app-metrics`](/apps/platform/metrics-and-scores) store per-user data for you. |
| Consent flows for biometric data | Elata shows consent in its own trusted frame and enforces it server-side. |
| Hosting, CDN, TLS | Upload a `.zip`. Elata serves it on an isolated per-app origin. |
***
## How Your App Runs
Your app runs inside a sandboxed iframe on its own origin. It has no access to
Elata's cookies, the user's wallet, or Elata's backend. Everything it
needs from the platform goes through a small `postMessage` bridge that the
`@elata-biosciences/app-*` packages wrap for you. See
[Host bridge](/apps/platform/host-bridge).
***
## Build Steps
Start from a template with `npm create @elata-biosciences/elata-demo my-app`, or add the SDK to an existing app. See [Your first app](/sdk/tutorials/first-app).
Make sure your build meets the [requirements](/apps/build/requirements), including real SDK usage and an `index.html` entry.
Declare your release version in an [`elata.json`](/apps/build/elata-json) manifest.
Walk through the [Build an App wizard](/apps/build/launch-your-app).
***
## Next
What your app needs
The wizard, step by step
How your app talks to Elata
# Post-Launch Checklist
Source: https://docs.elata.bio/apps/build/post-launch-checklist
What to do once your app is live on Elata.
## Right after approval
Open `app.elata.bio/app/` and confirm the icon, header, description, and screenshots look right on desktop and mobile.
Run a full session from the play page while signed in, and again signed out, so you see what new users see.
Add team members and social links from the **Edit** page so users can find your community.
Keep the app **Public** to appear in discovery, or **Unlisted** to share it only by link.
***
## Before accepting payments
* **Check the payout address** in **Edit → Commerce → Funds** and confirm you control that wallet.
* **Save products and plans** using their own save actions in Commerce. Confirm active products have the right identifiers and prices.
* **Test as a separate user**, because owners and team members bypass app subscription gating.
* **Verify item restoration** after reload and when an item is deactivated.
* **Check subscription dates**, trial eligibility, current-month coverage, next-month renewal, and expired access.
* **Exercise payment states**, including signed-out, no wallet, insufficient funds, cancellation, and unavailable checkout.
* **Verify a completed payment** in user Billing, creator Commerce, and the payout wallet. Check the network before using funds in a preview.
* **Make your offer clear**: items are one-time unlocks; app subscriptions renew manually; subscription access does not include all paid items.
Follow [Monetize your app](/apps/build/monetize) for setup and [Revenue and payouts](/apps/operate/revenue-and-payouts) to access the proceeds.
***
## Launch week
* **Share your app link.** Shared links are tracked, and visits from them count toward the sharer's [Elata Points](/overview/points).
* **Reply to reviews and comments** on your app page.
* **Add an in-app feedback entry** so users can reach you from inside the app. See [Host bridge](/apps/platform/host-bridge#feedback).
* **Watch your analytics** in **Edit → Analytics**.
***
## Ongoing
* Ship improvements as new releases. Each goes through review, and your current release stays live until the new one is approved.
* Keep your changelog accurate, especially for changes in monetization or data use.
* Consider [in-app purchases](/apps/platform/in-app-purchases) or a [subscription plan](/apps/platform/subscriptions).
***
## Next
Growth playbook
What to track
# Requirements
Source: https://docs.elata.bio/apps/build/requirements
What your app needs before you upload and submit it.
## Checklist
Sign in at [app.elata.bio](https://app.elata.bio). You can sign in with a social login or connect an existing wallet.
Registering an app name costs a small one-time fee in USDC. The current amount is shown in the wizard.
Uploads are rejected if no Elata SDK usage is detected. Use camera rPPG (`rppg-web`), EEG over Bluetooth (`eeg-web` + `eeg-web-ble`), or headband PPG (`ppg-web`, which runs on the EEG transport).
A `.zip` with `index.html` at its root (or a single `.html` file). Asset paths must be **relative**, because apps are served under a sub-path. See [Upload and hosting](/apps/build/upload-and-hosting).
Add an [`elata.json`](/apps/build/elata-json) so Elata shows `v1.2.3` instead of a release number.
An app icon and a header image are required to submit. Screenshots and an extended description are recommended. See [App listing](/apps/build/app-metadata).
***
## SDK usage check
Elata looks for real calls into the Elata SDK in your bundle, for example
`createRppgSession(...)` from `rppg-web`, or `initEegWasm(...)` and
`BleTransport` from the EEG packages, along with the packaged WASM files.
Importing a package without using it is not enough.
The templates from `npm create @elata-biosciences/elata-demo` already pass
this check. If you integrate the SDK by hand, keep the packaged `.wasm` files
in your build output.
***
## Things your app must not do
Apps run in a sandbox. Reviews and the automated scan reject apps that:
* start wallet transactions, signatures, or approvals outside Elata's own checkout
* send raw camera frames, microphone audio, or raw biosignals off the device
* try to reach the parent page, its cookies, or storage, or escape the sandbox
* impersonate another app, developer, or service
The full list is in [Review and policy](/apps/build/review-and-policy).
***
## Next
Walk through the wizard
The app manifest
# Review and Policy
Source: https://docs.elata.bio/apps/build/review-and-policy
What the automated scan and moderators check before an app goes live.
## How review works
Every release is checked twice before it goes live:
1. **Automated security scan.** Elata scans your bundle for known malicious patterns, wallet-draining code, sandbox-escape attempts, obfuscated code, and missing SDK usage. A model-assisted code review runs alongside the pattern checks.
2. **Moderator review.** An Elata moderator reviews the release, its listing, and the scan results, then approves or rejects it.
A release that fails the scan is held back and can't be approved until it's
resolved.
To keep the scan effective, detailed security findings aren't returned to
uploaders. Upload-time problems you *can* fix yourself, such as a missing
`index.html` or no SDK usage, are always shown to you directly.
***
## The submission policy
The full text is published at
[elata.bio/terms#developer-app-submission](https://elata.bio/terms#developer-app-submission).
In short, your app must not:
No token transfers, signatures, or approvals except through Elata's own checkout. No unlimited spending approvals, and no disguising what a transaction does.
Raw biometric data, camera frames, and microphone audio must not leave the user's device or go to a third party. Derived, on-device results (for example a calm score) are fine if disclosed to the user. The one first-party exception is sending derived session results to Elata through [`reportAffect`](/apps/platform/metrics-and-scores#reportaffect), with the user's consent.
No copying another app's or company's name, branding, icons, or domain.
No attempts to reach the parent page, bypass the frame's isolation, or work around Elata's security controls. Please report sandbox defects through responsible disclosure instead.
Name, description, screenshots, and changelog must accurately describe the app. Material changes in behavior or monetization belong in your release changelog.
Violations can lead to a release being removed, or an app or developer account
being suspended or banned.
***
## Tips for a smooth review
* Keep your bundle readable. Heavily obfuscated code draws extra scrutiny.
* Only request camera or Bluetooth access when the user starts a session.
* Make sure the app works in Elata's play window before submitting.
* Describe in your listing what the app measures and what it does with the data.
# Upload and Hosting
Source: https://docs.elata.bio/apps/build/upload-and-hosting
Upload limits, self-hosted URLs, the sandbox your app runs in, and home-screen install.
## Two ways to host
| Option | How it works | When to use it |
| - | - | - |
| **Elata-hosted** (default) | Upload a `.zip` or a single `.html` file. Elata serves it. | Almost always |
| **Self-hosted** | Give Elata a public `https://` URL. Elata embeds it. | Apps that need their own server. Requires approval from Elata. |
Uploaded code is treated as static web content (HTML, CSS, JS, and assets). It
runs in the user's browser and is never executed on Elata's servers.
***
## Limits
| Limit | Value |
| - | - |
| Upload size | 200 MB |
| Unzipped size | 500 MB |
| Files in a zip | 2,000 |
| Single file size | 100 MB |
The zip must contain `index.html`. File paths must be relative and plain: no
absolute paths, no `..` segments, and no control characters.
***
## Building for Elata
* **Use relative URLs.** Apps are served under a sub-path, not at the domain root. Load assets and data with relative paths (`./data.json`, not `/data.json`).
* **Ship the WASM files.** Keep the SDK's packaged `.wasm` files in your build output.
* **Use Elata's bridge, not browser storage, for anything that matters.** Each app runs on its own origin, so `localStorage` works but stays on one device. Use [`app-state`](/apps/platform/app-state) for data that should follow the user.
* **Ask for camera access only when needed.** rPPG apps run in the Elata frame and the browser will prompt for camera permission.
***
## The sandbox
When someone plays your app, Elata loads it in a sandboxed iframe on an
isolated origin for your app. In that sandbox your app:
* **can** run scripts, use the camera (with permission), use Web Bluetooth in supported browsers, use pointer lock, and use its own storage
* **cannot** read Elata's cookies or storage, see the user's wallet or session, or navigate the Elata page
To talk to Elata, use the [host bridge](/apps/platform/host-bridge).
***
## Self-hosted apps
To use your own URL, request external hosting from your app's **Edit** page.
Once Elata approves the request you can publish releases that point at an
`https://` URL.
Self-hosted URLs must:
* use `https://` and be publicly reachable
* allow being embedded in a frame (don't send `X-Frame-Options: DENY` or a CSP `frame-ancestors` that excludes `app.elata.bio`)
Self-hosted URLs are also checked against a URL-reputation service.
***
## Home-screen install (PWA)
When someone installs your app from its play page (**Add to Home Screen**), Elata
makes the install use **your** app's name and icon if your bundle ships a
web manifest:
1. Add `manifest.webmanifest` (or `manifest.json`) next to `index.html`.
2. Link it from your HTML: ``. Use relative paths.
3. Include at least one icon. A 512×512 maskable PNG works best.
4. Set `name`, `short_name`, `theme_color`, and `background_color`.
Elata manages `start_url`, `scope`, and `display` so the installed app
reopens correctly. Without a manifest, the install uses your Elata listing's
name and image.
***
## Next
How your app talks to Elata
Icon, header, screenshots
# Choose Your Path
Source: https://docs.elata.bio/apps/choose-your-path
Whether you want to build an app or use one, start here.
## For Builders
You want to publish an app on Elata.
What Elata gives you and what you avoid building
What your app needs before you submit
Step-by-step through the Build an App wizard
Purchases, storage, scores, consent, and notifications
***
## For Users
You want to discover and play apps.
Find, play, and save apps
Manage app purchases and subscriptions
Earn points for taking part
***
## Not Sure?
Start with [What Are Elata Apps?](/apps/what-are-elata-apps) for the big picture, or jump to [What You Can Build](/apps/what-you-can-build) for examples.
# Analytics
Source: https://docs.elata.bio/apps/operate/analytics-and-health
The usage analytics Elata gives you, and how to get the most from them.
## Your analytics page
Open **My Apps → your app → Edit → Analytics** while signed in as the app's owner
or a team member. The earlier `/app//analytics` route redirects to the Analytics section of the edit console. It shows aggregate usage for a time window (90 days by
default):
| Metric | What it counts |
| - | - |
| **Users** | Distinct players who recorded data in your app |
| **Sessions** | Score submissions through `saveScore`, rather than independently counted play sessions |
| **Events** | All stored `app-metrics` rows in the window, including event records and score rows |
| **Avg score** | The average of scores saved with `saveScore` |
A usage chart shows the trend over the window.
***
## Privacy built in
Analytics are aggregate and anonymized:
* No individual users or raw payloads are ever shown.
* Results are suppressed below five distinct users, so very new apps may show no data at first.
* Totals and trends are computed from at most 20,000 recent rows; the distinct-user count is computed separately. Treat very high-volume totals as potentially partial.
***
## Make your analytics useful
Analytics are built from what your app records through
[`app-metrics`](/apps/platform/metrics-and-scores). To get meaningful numbers:
* **Record meaningful events**, for example `session_complete` or `level_complete`, not every frame.
* **Save scores** with `saveScore` when a session produces a result. This drives **Avg score** and the current **Sessions** count. Multiple score submissions in one play session increase that count multiple times.
* **Keep event types stable** across releases so trends stay comparable.
***
## What to watch
| Signal | What it may mean |
| - | - |
| Users rising, sessions per user flat | New players arrive but don't return. Look at onboarding and saved progress. |
| Sessions rising, avg score rising | Players are getting better. Consider harder content. |
| Drop after a release | Check the new build in the play window and read recent reviews. |
Your app page also shows plays, saves, votes, reviews, and comments.
***
## Compare usage with sales
Open **Edit → Commerce** for product sales, subscription payments, and recent transactions. Use **Edit → Analytics** to understand usage. These panels answer different questions: paid access does not guarantee someone played, and trial access does not imply a direct app payment.
Usage analytics remain aggregate and subject to the minimum-cohort rule. Commerce transaction records are a separate operational view and may display shortened buyer wallet addresses.
Product revenue estimates use current catalog prices; subscription revenue uses recorded payment amounts. Neither is a net wallet balance. See [Revenue and payouts](/apps/operate/revenue-and-payouts) for interpretation and access to funds.
# Grow Your Community
Source: https://docs.elata.bio/apps/operate/grow-your-community
A practical playbook for getting players to your app and keeping them.
## Launch week
The first week sets the tone. Focus on getting people to play, and making it
easy for them to talk to you.
1. **Share your Elata link everywhere.** Post `app.elata.bio/app/` on X, Discord, and relevant communities. Shared links are tracked, so people who share your app earn [Elata Points](/overview/points) when others click through.
2. **Fill in your listing.** A clear header image, screenshots, and an extended description noticeably improve conversion. See [App listing](/apps/build/app-metadata).
3. **Add your team and social links** so players can see who's behind the app.
4. **Put a feedback button inside your app** using Elata's feedback channel. See [Host bridge](/apps/platform/host-bridge#feedback).
***
## Keep players coming back
* **Save progress** with [`app-state`](/apps/platform/app-state) so players pick up where they left off, on any device.
* **Show progress over time.** Record sessions and scores with [`app-metrics`](/apps/platform/metrics-and-scores) and show personal bests or trends.
* **Use reminders sparingly.** Elata-brokered [notifications](/apps/platform/notifications) are opt-in and rate-limited. Use them for things the player asked for, such as "session complete".
* **Ship regular updates** and describe them in your release changelog.
* **Reply to reviews and comments** on your app page.
***
## Discovery
Elata ranks apps using signals such as plays, saves, votes, reviews, and
recency, and personalizes recommendations for signed-in users. New apps start
with limited exposure and gain visibility as people engage with them.
Users can also:
* **save** your app to their collection
* **vote** and **review** it
* **follow** you and other players
* share it with a tracked link
***
## Social presence
| Platform | Use it for |
| - | - |
| X / Twitter | Announcements and release updates |
| Discord | Support, feedback, and community |
| GitHub | Open source code and issues |
| Website | A landing page outside Elata |
***
## Next
What to track
Monetize with one-time items
# Revenue and Payouts
Source: https://docs.elata.bio/apps/operate/revenue-and-payouts
Understand your sales reports, access your payout wallet, and move your USDC.
Your share of direct app sales goes to your app's payout wallet after the splitter deducts the configured platform fee. You access that wallet to move the money; there is no separate Elata creator balance to withdraw.
## Open your commerce dashboard
Go to **My Apps → your app → Edit → Commerce**, at `/app//edit#commerce` on the appstore.
| Panel | What it shows |
| - | - |
| Products | Your catalog and active state |
| Monthly subscription | The app's price, trial settings, active state, and subscriber signal |
| Revenue | Product sales and estimated product revenue, recorded subscription payments, and estimated total gross revenue |
| Transactions | Recent product and subscription records, with transaction links when available |
| Funds | The payout address and that wallet's current USDC balance on Base |
## Understand the numbers
**Product revenue is estimated.** Product purchase records do not store a historical paid amount. The report multiplies purchase counts by each item's current price. Editing a price can change the estimate for earlier sales without changing what anyone actually paid.
**Subscription revenue uses recorded amounts.** Paid periods store the amount charged at payment time. Free trials are not paid subscription revenue.
**Revenue is gross.** The report does not calculate your exact net earnings after platform fees. If it says the source records were limited, totals may also be partial. Use transaction records to reconcile actual payments and recipient amounts.
**Funds is a wallet balance.** It includes other USDC deposited in that wallet and excludes funds already sent out. Several apps can share a wallet. A zero balance does not mean the app has never earned revenue.
## Where payouts go
The current app payment destination is the app creator's wallet, displayed as the payout wallet in **Funds**. Products and the monthly plan do not have their own editable payout-address field.
Before selling, compare the displayed address with the wallet you control. If it is wrong or inaccessible, resolve it with [support](/users/help) before accepting payments. Adding a team member does not transfer ownership or configure automatic revenue sharing.
## Access your funds
In **Commerce**, find **Funds**, check the full payout address, and read **USDC balance (Base)**. Refresh or retry if the balance cannot be loaded.
Sign in as the app owner and connect the wallet associated with the payout address. The console offers **Manage funds** only when its owner and wallet checks pass. A team member can view the report but does not get this owner-only action.
This opens wallet controls. Available send, receive, and export options depend on the wallet type. Check the wallet address inside the opened interface against the payout address before transferring anything.
The dashboard's permissions control its own buttons. Anyone who independently controls the payout wallet can also manage that wallet outside Elata.
## Send USDC to another wallet
1. Open the payout wallet, using **Manage funds** or your wallet's own application.
2. Select **USDC on Base** and the send action.
3. Enter the destination address and amount. Confirm that the destination accepts USDC on Base.
4. Review any network fee or sponsorship details and confirm the transfer.
5. Save the transaction hash and check the destination balance after confirmation.
An external wallet may require ETH on Base for network fees. Embedded or smart wallets may handle fees differently; use the details shown by the wallet. Export, when supported, exports wallet access rather than converting USDC to cash.
## Get money into a bank account
Elata's Funds card does not provide a bank withdrawal or convert USDC to fiat. To cash out:
1. Use an exchange or cash-out service available to you that explicitly accepts **USDC deposits on Base** and offers withdrawals to your bank.
2. Obtain its USDC deposit address with **Base** selected as the deposit network.
3. Send USDC from your payout wallet to that address.
4. After the service credits the deposit, follow its conversion and bank withdrawal steps.
Check that service's current deposit minimums, fees, verification requirements, and bank withdrawal options before sending. A deposit address for a different network is not enough. Those steps happen with the receiving service, outside Elata.
## Pay collaborators
Team access lets collaborators help manage the app. It does not automatically split direct app revenue among them. The current flow sends the creator's share to one payout wallet. To pay collaborators from those proceeds, make transfers from that wallet according to your own agreed arrangement.
## Troubleshooting
| Problem | What to check |
| - | - |
| Manage funds is missing | Owner status, wallet connection, and the displayed payout address |
| The wallet opened on a different address | Close it and select the actual payout wallet before sending |
| Balance does not match Revenue | Gross versus net, estimated item prices, funds already sent, other deposits, and shared wallets |
| A customer's payment is missing from reports | Settlement and recording state; compare the transaction hash with the app's records |
| Balance cannot be loaded | Retry the balance query; a read failure is not evidence funds are gone |
| You need a payout-address change | Contact support; product and plan settings do not change the payout destination |
For missing or disputed payments, keep the app identifier, date, item or paid period, and transaction hash. Use [Help](/users/help). The dashboard does not provide self-service refunds, payout-address replacement, or a bank cash-out button.
## Related guides
* [Monetize your app](/apps/build/monetize)
* [In-app purchases](/apps/platform/in-app-purchases)
* [Subscriptions](/apps/platform/subscriptions)
* [Analytics](/apps/operate/analytics-and-health)
* [The user billing experience](/users/billing-and-purchases)
# App State
Source: https://docs.elata.bio/apps/platform/app-state
Per-user, per-app key-value storage with @elata-biosciences/app-state.
`@elata-biosciences/app-state` is not published to npm yet. Until it is, you can call Elata directly with the `elata:state:*` messages shown below; Elata already supports them.
## Overview
`@elata-biosciences/app-state` stores small JSON values per user and per app:
save games, settings, progress. You don't need a backend, and the bridge does not disclose the user's account credentials. Values follow the user across devices.
```bash theme={null}
# Not on npm yet. This command works after the first release.
npm install @elata-biosciences/app-state
```
```ts theme={null}
import { getState, setState, deleteState } from '@elata-biosciences/app-state';
// null if nothing was ever stored
const save = await getState('save_slot_1');
await setState('save_slot_1', { level: 7, hp: 100 });
// idempotent: no error if the key was never set
await deleteState('save_slot_1');
```
***
## Limits
| Limit | Value |
| - | - |
| Key | 1 to 256 characters from `A-Z a-z 0-9 . _ : / -` |
| Value | Any JSON-serializable value, up to 64 KB once encoded |
| Scope | One namespace per (user, app). Apps can't read each other's data. |
***
## Errors
All three functions reject with `AppStateError`, which has a `code`:
| Code | Where it comes from | Meaning |
| - | - | - |
| `no_parent` | Local | Not running inside Elata's frame |
| `timeout` | Local | No reply in time |
| `invalid_input` | Local or Elata | Bad key or value |
| `not_authenticated` | Elata | The user isn't signed in |
| `value_too_large` | Elata | Value is over 64 KB |
| `fetch_failed` | Elata | Elata couldn't reach storage |
Players who aren't signed in get `not_authenticated`. Keep a local fallback,
such as `localStorage`, and sync to `app-state` once they sign in.
***
## Without the package
Elata answers these messages directly. Send them to `window.parent` with a unique `requestId` and wait for the matching `…:result` reply:
```js theme={null}
function stateCall(type, payload) {
return new Promise((resolve, reject) => {
const requestId = crypto.randomUUID();
const onMessage = (e) => {
if (e.data?.type !== `${type}:result` || e.data.requestId !== requestId) return;
window.removeEventListener('message', onMessage);
e.data.error ? reject(new Error(e.data.error)) : resolve(e.data);
};
window.addEventListener('message', onMessage);
window.parent.postMessage({ type, requestId, ...payload }, '*');
});
}
const { value } = await stateCall('elata:state:get', { key: 'save_slot_1' });
await stateCall('elata:state:set', { key: 'save_slot_1', value: { level: 7 } });
await stateCall('elata:state:delete', { key: 'save_slot_1' });
```
Add a timeout in production: outside Elata nothing replies.
***
## app-state or app-metrics?
| Use | Package |
| - | - |
| The latest value of something: settings, a save slot, unlocked levels | `app-state` |
| A history of events or scores you want to query and chart | [`app-metrics`](/apps/platform/metrics-and-scores) |
# Consent and Insights
Source: https://docs.elata.bio/apps/platform/consent-and-insights
Ask for biometric data-sharing consent and read a user's cross-app insights.
## The biometric Score
Elata keeps a cross-app **biometric Score** for each user, built from derived
session results that apps report with
[`reportAffect`](/apps/platform/metrics-and-scores#reportaffect). Nothing is
shared until the user explicitly opts in, and they can turn it off at any time.
Consent is shown by Elata in its own trusted frame, not by your app. Elata
also re-checks consent on the server for every sample, so your app can't
contribute without it.
There's currently one consent purpose: `platform_score`.
***
## Ask for consent
```js theme={null}
const requestId = crypto.randomUUID();
window.addEventListener('message', (e) => {
if (e.data?.type === 'elata:consent:state' && e.data.requestId === requestId) {
console.log('granted?', e.data.granted);
}
});
window.parent?.postMessage(
{ type: 'elata:consent:request', purpose: 'platform_score', requestId },
'*',
);
```
Elata opens its consent dialog and replies with
`{ type: 'elata:consent:state', purpose, granted, requestId }` once the user
decides.
To let users **turn sharing off** from your app's settings, send the same
message with `action: 'revoke'`.
***
## Read consent without prompting
To show the current state in your UI, for example as a settings toggle, send
`elata:consent:query`. You get the same reply, and no dialog is shown.
```js theme={null}
window.parent?.postMessage(
{ type: 'elata:consent:query', purpose: 'platform_score', requestId: 'q1' },
'*',
);
```
***
## Read the user's insights
Users who share their Score can see their own cross-app results inside your
app. Request them with `elata:insights:request`:
```js theme={null}
window.parent?.postMessage(
{ type: 'elata:insights:request', requestId: 'i1', windowDays: 90 },
'*',
);
```
Elata replies with `elata:insights:state`:
```ts theme={null}
{
type: 'elata:insights:state',
requestId?: string,
consented: boolean, // false → no data; prompt the user to opt in
data?: {
engagement: {
activeDays: number,
currentStreak: number,
longestStreak: number,
appsUsed: number,
totalScores: number,
lastActiveAt: number | null,
},
apps: { slug: string | null, name: string | null }[],
score: { value: number | null, calibrating: boolean, sampleCount: number },
windowDays: number,
generatedAt: number,
}
}
```
`windowDays` defaults to 90 and is capped at 365. The data is a curated summary
of the user's **own** activity; it never includes other users or raw records.
***
## Checklist for Score-contributing apps
1. Ask the Elata team to enable the `biometrics` scope for your app.
2. Explain in your app and listing what is measured and what is shared.
3. Ask for consent with `elata:consent:request` before calling `reportAffect`.
4. Handle `scope_denied` from `reportAffect` by offering the consent prompt again.
# Host Bridge
Source: https://docs.elata.bio/apps/platform/host-bridge
How your sandboxed app talks to Elata, and every message it supports.
## Overview
Your app runs in a sandboxed iframe with no access to Elata's cookies,
backend, or the user's wallet. When it needs something from the platform, such
as a purchase, saved data, or consent, it sends a `postMessage` to the parent
frame. Elata does the work with the user's session and replies.
The bridge does not expose account credentials or session tokens. Public activity, commerce records, and information a user enters are separate from this isolation.
```
┌──────────── app.elata.bio (Elata) ────────────┐
│ session · checkout · storage · consent UI │
│ ▲ │ │
│ │ postMessage │ reply │
│ ┌─────┴─────────────────────────▼─────────┐ │
│ │ your app (sandboxed, own origin) │ │
│ └─────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘
```
***
## Use the SDK packages
For most features there's a small package that wraps the messages, timeouts,
and errors for you:
| Feature | Package | Page |
| - | - | - |
| One-time purchases and ownership | `@elata-biosciences/app-payments` | [In-app purchases](/apps/platform/in-app-purchases) |
| Per-user key-value storage | `@elata-biosciences/app-state` (not yet on npm) | [App state](/apps/platform/app-state) |
| Event records, scores, `reportAffect` | `@elata-biosciences/app-metrics` | [Metrics and scores](/apps/platform/metrics-and-scores) |
Consent, insights, notifications, navigation, and feedback use plain
`postMessage` calls. They're documented below and on their own pages.
***
## Ground rules
* **Send to the parent.** Post to `window.parent`. Use the known host origin as the target when available. SDK clients handle their own transport. Hand-written listeners should verify the replying window and, when known, its origin; do not assume every bridge handler enforces identical checks.
* **Use a `requestId`.** Where a reply is expected, include a unique `requestId` (8 to 128 characters; `crypto.randomUUID()` works) and match it on the reply.
* **Handle no reply.** Outside Elata (for example in local development) nobody answers. Use a timeout and degrade gracefully. The SDK packages do this for you.
* **Elata is the source of truth.** Prices, ownership, and consent always come from Elata, never from values your app sends.
***
## Message reference
### Purchases
| App → Elata | Elata → app |
| - | - |
| `elata:iap:request` `{ requestId, contentId, priceUsdc?, title?, description? }` | `elata:iap:result` `{ requestId, status: "success" \| "cancelled" \| "error", txHash?, error? }` |
| `elata:iap:getCatalog` `{ requestId }` | `elata:iap:getCatalog:result` `{ requestId, items }` |
| `elata:iap:hasItem` `{ requestId, contentId }` | `elata:iap:hasItem:result` `{ requestId, owned }` |
| `elata:iap:listOwned` `{ requestId }` | `elata:iap:listOwned:result` `{ requestId, ownedContentIds }` |
Use [`app-payments`](/apps/platform/in-app-purchases) instead of sending these by hand.
### App state
| App → Elata | Elata → app |
| - | - |
| `elata:state:get` `{ requestId, key }` | `elata:state:get:result` `{ requestId, value }` or `{ requestId, error }` |
| `elata:state:set` `{ requestId, key, value }` | `elata:state:set:result` `{ requestId, ok }` or `{ requestId, error }` |
| `elata:state:delete` `{ requestId, key }` | `elata:state:delete:result` `{ requestId, ok }` or `{ requestId, error }` |
Once `@elata-biosciences/app-state` is on npm, prefer it over sending these by hand. See [App state](/apps/platform/app-state).
### Metrics
`app-metrics` uses a dedicated `MessageChannel` set up with a
`__elata_metrics_init` handshake, not individual window messages. Always use
the [package](/apps/platform/metrics-and-scores).
### Consent and insights
| App → Elata | Elata → app |
| - | - |
| `elata:consent:request` `{ requestId?, purpose: "platform_score", action?: "grant" \| "revoke" }` | `elata:consent:state` `{ requestId?, purpose, granted }` |
| `elata:consent:query` `{ requestId?, purpose: "platform_score" }` | `elata:consent:state` `{ requestId?, purpose, granted }` |
| `elata:insights:request` `{ requestId?, windowDays? }` | `elata:insights:state` `{ requestId?, consented, data? }` |
See [Consent and insights](/apps/platform/consent-and-insights).
### Notifications
| App → Elata | Elata → app |
| - | - |
| `elata:notify:request` `{ requestId?, body }` | `elata:notify:state` `{ requestId?, status }` |
See [Notifications](/apps/platform/notifications).
### Navigation
Send the user to another app's play page in Elata:
```js theme={null}
window.parent?.postMessage({ type: 'elata:navigate', slug: 'calm-runner' }, '*');
```
`slug` must be an Elata app slug: lowercase letters, digits, and dashes, up to
64 characters. There's no reply.
### Feedback
Elata's floating feedback button is hidden while an app is playing, so it
doesn't cover your controls. Open Elata's feedback panel from your own UI
instead:
```js theme={null}
window.parent?.postMessage({ type: 'elata:feedback:open', mode: 'feature' }, '*');
// mode: 'feature' (default) or 'bug'
```
There's no reply.
# In-App Purchases
Source: https://docs.elata.bio/apps/platform/in-app-purchases
Sell one-time items in your app with @elata-biosciences/app-payments.
## Overview
`@elata-biosciences/app-payments` lets your app open Elata's checkout and
check what the user owns. Elata runs checkout, takes payment in USDC, and
records ownership. Your app never touches wallets or payment details.
```bash theme={null}
npm install @elata-biosciences/app-payments
```
| Function | Returns | Use it to |
| - | - | - |
| `getCatalog(opts?)` | `Promise` | List your app's items with price, title, and description |
| `getOwnedItems(opts?)` | `Promise` | List every `contentId` the user owns |
| `hasItem(contentId, opts?)` | `Promise` | Check one item |
| `requestPurchase(input)` | `Promise` | Open checkout for an item |
## Create your catalog
Open **My Apps → your app → Edit → Commerce → Products**. Owners and authorized team members can create and manage the catalog themselves.
| Field | What to enter |
| - | - |
| Title | A clear item name, up to 100 characters |
| Description | What the buyer receives, up to 500 characters |
| Image URL | Optional HTTP or HTTPS image URL |
| Price | A positive human-readable USDC amount in the dashboard form |
| Active | Whether the item is available in the active catalog |
Save the product, then copy its assigned `contentId` into your integration. The identifier is stable and cannot be edited. Products can be edited or deactivated rather than deleted. Deactivation stops new sales; keep applying existing owners' benefits, including when the item no longer appears in the active catalog.
The dashboard accepts ordinary amounts such as `1.00`. Product APIs and catalog responses use integer USDC base units: `"1000000"` means 1 USDC. Creating a catalog entry does not implement its benefit in your app.
Checkout must be configured for the deployment. The app checkout described here pays USDC on Base through the splitter. See [What's live](/overview/status) before testing payments.
***
## The correct flow
Gate your UI on ownership first, then purchase, then apply the benefit.
```ts theme={null}
import {
getCatalog,
getOwnedItems,
requestPurchase,
AppPaymentsError,
} from '@elata-biosciences/app-payments';
const formatUsdc = (base: string) => `$${(Number(base) / 1e6).toFixed(2)}`;
// 1. Read the catalog and what the user already owns.
const [items, owned] = await Promise.all([getCatalog(), getOwnedItems()]);
const ownedSet = new Set(owned);
// 2. Re-apply benefits on startup. Elata is the source of truth,
// so this survives reloads and device changes.
for (const id of owned) applyBenefit(id);
// 3. Buy, then branch on status.
async function buy(item) {
try {
const result = await requestPurchase({
contentId: item.contentId,
priceUsdc: formatUsdc(item.priceUsdc), // display hint only
title: item.title,
});
if (result.status === 'success') {
ownedSet.add(item.contentId);
applyBenefit(item.contentId);
} else if (result.status === 'error') {
showError(result.error);
}
// 'cancelled': the user backed out. Do nothing.
} catch (err) {
// Only thrown for local problems: no parent frame, bad input, timeout.
showError(err instanceof AppPaymentsError ? err.code : String(err));
}
}
```
`requestPurchase` records *that* the user owns an item. Your app decides what
owning it *does*.
***
## Things to know
If the user already owns the item, Elata skips payment and returns `{ status: "success", txHash: "" }`. Code that checks `if (result.txHash)` will mishandle this.
`CatalogItem.priceUsdc` is in USDC **base units** (6 decimals): `"50000"` is \$0.05. `RequestPurchaseInput.priceUsdc` is a free-form display hint Elata ignores. Elata always charges the catalog price.
Each item is bought once. Consumables (spend-per-use balances) and recurring billing aren't supported through items. For recurring revenue use a [subscription plan](/apps/platform/subscriptions).
`requestPurchase` waits up to 5 minutes for the user to finish checkout. `getCatalog`, `getOwnedItems`, and `hasItem` default to 10 seconds. Outside Elata, use a mock host for local tests. Treat an ownership-query failure as unknown ownership: offer a retry or sign-in action, and never grant a paid benefit merely because the query timed out.
***
## Errors
Calls reject only with `AppPaymentsError`, which has a `code`:
| Code | Meaning |
| - | - |
| `invalid_input` | `contentId` isn't a non-negative integer, or an option has the wrong type |
| `no_window` | Not running in a browser |
| `no_parent` | Not running inside Elata's frame |
| `no_crypto` | `crypto.randomUUID` isn't available |
| `timeout` | No reply in time |
| `not_authenticated` | The user isn't signed in (ownership queries) |
| `fetch_failed` | Elata couldn't look up ownership |
A normal `cancelled` or `error` purchase outcome resolves; it doesn't reject.
***
## Security model
* Elata's purchase records are created after server-side payment verification. Your UI should use host ownership queries as its source of truth.
* Your app **never sees** payment details, wallet keys, or session tokens.
* Replaying a recording request with the same `requestId` is idempotent. This does not make sending a second on-chain payment safe.
* The splitter deducts the configured platform fee and sends the creator's share to the payout wallet. See [Revenue and payouts](/apps/operate/revenue-and-payouts).
***
## Try it
* [`examples/iap-demo`](https://github.com/Elata-Biosciences/elata-bio-sdk/tree/main/examples/iap-demo): one self-contained HTML file with a built-in mock store, so you can run the full flow locally.
* [Package README on npm](https://www.npmjs.com/package/@elata-biosciences/app-payments)
## Payment and UI states
Handle success, cancellation, and errors separately. Prevent overlapping purchase attempts. A successful payment and a recorded entitlement are separate steps; unlock only after Elata reports success.
If a payment settles but recording fails, keep the transaction details and direct the user to [billing help](/users/billing-and-purchases#payment-problems-and-history). Do not automatically send another payment. The checkout retries temporary indexing delays, but recovery from every recording failure is not guaranteed.
Ownership queries can require authentication. On `not_authenticated`, ask the user to sign in; on a lookup failure, show a retry state. Do not silently treat unknown ownership as a confirmed non-owner.
The app does not connect wallets itself. Elata checkout manages wallet connection and payment confirmation, including the embedded-wallet interface when used. Credits for metered features are not an alternative payment balance for these items.
## Test your integration
* Reopen the app and restore already-owned benefits from host ownership.
* Show **Use** or **Owned** for purchased items and **Buy** for available unowned items.
* Handle a successful already-owned response with an empty transaction hash.
* Handle signed-out, cancelled, insufficient-funds, unavailable-checkout, and lookup-error states.
* Deactivate an item and verify that existing owners can still use its benefit.
For subscription access, use [Subscriptions](/apps/platform/subscriptions), rather than simulating recurring billing with repeated item purchases.
# Metrics and Scores
Source: https://docs.elata.bio/apps/platform/metrics-and-scores
Record events and scores, and contribute to the biometric Score, with @elata-biosciences/app-metrics.
## Overview
`@elata-biosciences/app-metrics` gives your app a per-user store for **event
records** and **scores**, plus `reportAffect` for contributing to the user's
cross-app biometric Score.
```bash theme={null}
npm install @elata-biosciences/app-metrics
```
```ts theme={null}
import { createMetricsClient } from '@elata-biosciences/app-metrics';
const metrics = createMetricsClient();
await metrics.ready();
await metrics.record({ type: 'level_complete', data: { level: 3, time: 42 } });
await metrics.saveScore({ value: 1200, meta: { level: 3 } });
const best = await metrics.loadScores({ order: 'value_desc', limit: 10 });
const recent = await metrics.query({ type: 'level_complete', limit: 20 });
```
Data is scoped to the (user, app) pair and never crosses app boundaries. Elata
keeps a local copy and, for signed-in users, syncs it to the user's
account so it follows them across devices. These records also power your
[analytics](/apps/operate/analytics-and-health).
***
## API
| Method | What it does |
| - | - |
| `ready()` | Resolves once the connection to Elata is set up |
| `record({ type, data })` | Store an event record |
| `query({ type?, since?, until?, limit? })` | Read records back |
| `clear()` | Delete all of this app's records for the user |
| `saveScore({ value, meta? })` | Store a numeric score |
| `loadScores({ order?, since?, until?, limit? })` | Read scores. `order` is `"value_desc"` or `"timestamp_desc"`. |
| `reportAffect(report)` | Contribute a session result to the biometric Score (see below) |
| `dispose()` | Tear down the client |
### Limits
* Each record can be up to 64 KB.
* Each (user, app) pair has a 5 MB storage quota.
* Calls fail with `quota_exceeded`, `invalid_payload`, or `rate_limited` when you exceed limits.
Errors are thrown as `MetricsClientError`, whose `code` is one of the host codes
above or `handshake_timeout`, `disposed`, or `transport`.
***
## reportAffect
`reportAffect` sends a **derived session result**, never raw signal, to Elata
so it can contribute to the user's cross-app biometric Score.
```ts theme={null}
const result = await metrics.reportAffect({
dimension: 'calm', // 'calm' | 'stress' | 'focus'
baselineValue: 0.42, // 0..1
sessionValue: 0.61, // 0..1
delta: 0.19, // -1..1
meanHr: 68, // optional session-average heart rate
signalQuality: 0.85, // 0..1
confidence: 0.8, // 0..1
source: 'rppg',
durationSec: 300,
});
// result: { accepted, calibrating, score }
```
| Result field | Meaning |
| - | - |
| `accepted` | `false` if the sample was dropped, for example for low quality |
| `calibrating` | `true` until the user has enough samples for a stable Score |
| `score` | The user's Score, 0 to 100, once calibrated; otherwise `null` |
Two things must be true or the call fails with `scope_denied`:
1. **Your app has the `biometrics` scope.** Contact the Elata team to enable it for your app.
2. **The user has consented.** Ask with [`elata:consent:request`](/apps/platform/consent-and-insights). Elata also re-checks consent on the server for every sample.
***
## For hosts
The package also ships a host entry, `@elata-biosciences/app-metrics/host`
(`createMetricsHost`), used by Elata itself. App developers don't need it.
# Notifications
Source: https://docs.elata.bio/apps/platform/notifications
Show reminders to users through Elata while your app is open.
## Why your app can't notify directly
Your app runs in a sandboxed, cross-origin iframe. Browsers block the
`Notification` API there: permission is stuck at `denied` and
`requestPermission()` never prompts.
There are two supported options:
| Option | Works when | Best for |
| - | - | - |
| **Elata-brokered reminders** (this page) | Elata tab is open | "Session complete", "time for your break" |
| **Background push** | A separately implemented service worker, push subscription, and delivery service | Background notifications; installing alone does not provide this |
***
## Request a reminder
```js theme={null}
window.parent?.postMessage(
{
type: 'elata:notify:request',
requestId: 'optional-id',
body: 'Focus block complete. Nice work!',
},
'*',
);
```
* `body` is plain text. Elata strips control characters, collapses it to one line, and cuts it to **140 characters**.
* You can't set the title, icon, click URL, or actions. Elata sets them.
Elata replies with `{ type: 'elata:notify:state', requestId?, status }`:
| status | Meaning | Suggested UI |
| - | - | - |
| `shown` | A notification was shown | Show reminders as on |
| `undecided` | The user hasn't opted in yet; Elata is showing an "Allow reminders?" prompt | A neutral, pending state |
| `blocked` | The user opted out, or the OS denied permission | Stop asking; explain notification settings |
| `rate_limited` | More than 3 requests in a minute | Back off |
| `unsupported` | This browser has no Notification API | Hide the setting |
***
## What Elata guarantees
* **Elata-authored title.** Always `Elata · `, taken from your Elata listing, so apps can't impersonate the OS, Elata, or each other.
* **Per-app opt-in.** Off by default. The first request shows an in-frame prompt, and the choice is remembered per app.
* **Rate limit.** At most 3 reminders per app per minute.
* **Foreground only.** Reminders appear only while Elata tab is open. Clicking one focuses Elata.
***
## Detecting Elata
Use the bridge only when running inside Elata, and keep your direct path for standalone use while its page is running. Home-screen installation alone does not let this code run when the app is closed:
```js theme={null}
const inStore = window.parent !== window;
if (inStore) {
window.parent.postMessage({ type: 'elata:notify:request', body }, '*');
} else if (Notification.permission === 'granted') {
new Notification('My App', { body });
}
```
# Subscriptions
Source: https://docs.elata.bio/apps/platform/subscriptions
Configure monthly app access, trials, and manual renewal through Elata.
An active subscription plan gates your whole app. Elata checks access before opening the app, so you do not need subscription checkout code inside it. You can also sell [one-time items](/apps/platform/in-app-purchases); their ownership is separate.
Paid checkout requires the deployment's payment configuration. See [What's live](/overview/status). App subscription checkout uses a connected wallet paying USDC on Base and renews manually; it is not an automatic recurring charge.
## Configure your plan
Open **My Apps → your app → Edit → Commerce → Monthly subscription**.
| Setting | Behavior |
| - | - |
| Monthly price | A positive amount in USDC; enter the human-readable amount in the form |
| Free trial | A whole number from 0 to 365 days; 0 means no trial |
| Active | An inactive plan does not gate the app or offer renewal |
Each app has one plan. Save using the plan's own action. Owners and authorized team members can manage it. Price edits do not rewrite previously recorded paid periods; review the current price when making a new checkout.
## Who gets access
| User | Access |
| - | - |
| App owner or authorized team member | Access without buying the app's plan |
| User with valid paid-period coverage | Access during that coverage |
| User with a valid trial | Access until the trial ends |
| User without any of these | Subscription prompt while the plan is active |
With no active plan, the app is free to enter. These rules concern app access; they do not grant paid in-app items.
## The customer's flow
1. The user opens the app's subscription prompt from its page or locked play screen.
2. If eligible, they start the offered trial without payment. Paid checkout can run directly in the subscription prompt when its wallet and payment configuration are ready, or the user can proceed to **Settings → Billing → Subscriptions**.
3. They choose available current-month or next-month coverage and review the cart.
4. Elata checks settlement and records access after payment. Multiple apps can be paid for in one cart.
Periods use calendar months in UTC. Next-month coverage starts on the first; current-month catch-up access, when offered, uses the coverage and prorated price shown in the cart. A trial does not cause an automatic charge when it ends.
The user must complete checkout to renew. An unpaid cart selection does not extend access. Leaving the next month unpaid lets paid access expire; removing a cart selection does not refund an already-paid period.
The previous `/profile/subscriptions` route redirects to the current [Billing → Subscriptions](https://app.elata.bio/settings/billing/subscriptions) page.
## Earnings and subscribers
Paid app periods retain their payment amounts. The **Commerce** report shows those subscription payments as gross revenue before the splitter fee. Your share settles directly to the creator's payout wallet. See [Revenue and payouts](/apps/operate/revenue-and-payouts).
The plan's subscriber count is an access signal, not an exact recurring revenue metric. Trials can contribute to the current-period count.
## Test before enabling
Use a separate user account to check the trial, paid-current-period, paid-next-period, lapsed, and unavailable-checkout states. Owners and team members bypass the gate, so testing only as yourself will miss it.
Tell users what the subscription includes and show the relevant end date. Avoid describing it as autopay or promising a refund action the billing UI does not provide.
## Next
* [Monetize your app](/apps/build/monetize)
* [Billing and purchases](/users/billing-and-purchases)
* [Revenue and payouts](/apps/operate/revenue-and-payouts)
# Quickstart
Source: https://docs.elata.bio/apps/quickstart
Build a biosignal app and publish it to Elata.
This guide takes you from nothing to a submitted app. You'll need Node.js 20 or
newer and a webcam.
**Known issue:** the repository's `create-elata-demo` 0.12.1 has a packaging blocker and fails to start without sibling workspace metadata. Verify a fixed release before using it. For now, follow
[Add rPPG to an existing app](/sdk/tutorials/rppg-existing-app) to set up
the SDK in your own web app instead. See [What's live](/overview/status).
Create a camera heart-rate (rPPG) starter app:
```bash theme={null}
npm create @elata-biosciences/elata-demo my-app -- --template rppg
cd my-app
npm install
```
Prefer EEG, headband PPG, or a game? See the [template list](/sdk/create-elata-demo).
```bash theme={null}
npm run dev
```
Open the local URL, allow camera access, and you should see a live pulse signal.
Edit the app in `src/`. The [rPPG guide](/sdk/guides/rppg-browser) explains the session API, and the [platform APIs](/apps/platform/host-bridge) add purchases, saved progress, and scores.
Create `public/elata.json`:
```json theme={null}
{ "version": "1.0.0" }
```
See [elata.json](/apps/build/elata-json).
```bash theme={null}
npm run build:zip
```
This writes `app.zip` with `index.html` at its root.
Go to [app.elata.bio/build-app](https://app.elata.bio/build-app), register your app name, upload `app.zip`, try it in the play window, add an icon and header image, and submit for review. The full walkthrough is in [Publish your app](/apps/build/launch-your-app).
***
## Next
A deeper SDK tutorial
What every upload needs
Talk to Elata from your app
# What Are Elata Apps?
Source: https://docs.elata.bio/apps/what-are-elata-apps
Browser apps built on the Elata SDK and published to Elata.
The repository's `create-elata-demo` 0.12.1 has a packaging blocker: missing `app-metrics` version metadata prevents startup. Check [scaffolder status](/sdk/create-elata-demo) before using the commands below; for now, follow [manual rPPG integration](/sdk/tutorials/rppg-existing-app).
## The Idea
An Elata app is a web app that uses the Elata SDK to work with the
body's signals: heart rate from a webcam, EEG from a consumer headband, or PPG
from a headband's optical sensor. You build it with standard web tooling,
upload it to the [Elata](https://app.elata.bio), and users play it
right in the browser.
You build the product. Elata handles hosting, sign-in, payments, per-user
storage, and distribution.
***
## What You Get When You Publish
| Capability | What it gives you |
| - | - |
| **Hosting** | Upload a static build (or point to your own HTTPS URL). Elata serves it inside a secure, sandboxed frame. |
| **Distribution** | an Elata listing at `app.elata.bio/app/` with discovery, reviews, comments, votes, and sharing. |
| **Accounts** | Users are already signed in to Elata. Your app never handles passwords, wallets, or session tokens. |
| **In-app purchases** | Sell one-time items through Elata checkout with [`app-payments`](/apps/platform/in-app-purchases). |
| **Subscriptions** | Offer a monthly plan with an optional free trial. See [Subscriptions](/apps/platform/subscriptions). |
| **Per-user storage** | Save games, settings, and scores with [`app-state`](/apps/platform/app-state) and [`app-metrics`](/apps/platform/metrics-and-scores). Signed-in users' data follows them across devices. |
| **Biometric Score** | With user consent, contribute derived session results to the user's cross-app Score. See [Consent and insights](/apps/platform/consent-and-insights). |
| **Reminders** | Send foreground notifications through Elata with [Notifications](/apps/platform/notifications). |
***
## How It Works
1. **Build** a web app with the [Elata SDK](/sdk/overview). The fastest start is `npm create @elata-biosciences/elata-demo my-app`.
2. **Register** your app name in the [Build an App](https://app.elata.bio/build-app) wizard.
3. **Upload** your build as a `.zip` (or give us an HTTPS URL) and try it in Elata's play window.
4. **Publish**: add an icon, header image, and description, then submit for review.
5. **Go live** once the release passes the automated security scan and a moderator approves it.
The full sequence is covered in [App Lifecycle](/apps/app-lifecycle).
***
## Who This Is For
* **Builders** who want to ship a biosignal app without building hosting, accounts, payments, and storage
* **Device makers** who want their sensor to reach an app ecosystem (see [Device Integration](/integrate))
* **Users** who want to discover and play biosignal apps
***
## Next
Builder or user?
From upload to live
Start building
# What You Can Build
Source: https://docs.elata.bio/apps/what-you-can-build
App categories, example ideas, and the biosignals you can build with.
## App Categories
Every app on Elata is listed in one of five categories.
| Category | What it covers | Example |
| - | - | - |
| **Games** | Casual or competitive games driven by the body's signals | A runner that speeds up as your heart rate calms down |
| **Neurometrics** | Measuring and visualizing brain and body signals | A live EEG band-power dashboard for a Muse headband |
| **Wellness** | Breathing, meditation, recovery, and stress tools | Guided breathwork with live rPPG heart-rate feedback |
| **Tools** | Utilities for training, focus, and self-tracking | A focus timer that logs a calm/stress trend per session |
| **Experiments** | Research-style apps, studies, and prototypes | A reaction-time study that records per-session results |
***
## Building With Biometrics
Elata apps are built around the [Elata SDK](/sdk/overview). Elata checks
every upload for real SDK usage, so your app must use at least one sensor path:
* **Camera rPPG** (`@elata-biosciences/rppg-web`): heart rate and pulse signals from any webcam, no extra hardware
* **EEG over Web Bluetooth** (`@elata-biosciences/eeg-web` + `eeg-web-ble`): brain signals from supported headbands such as Muse
* **Headband PPG** (`@elata-biosciences/ppg-web`): heart rate and HRV from a headband's optical sensor
On top of the sensor layer you can add recording and analysis with
[`biosignal-session`](/sdk/biosignal-session/getting-started) and
[`biosignal-analytics`](/sdk/biosignal-analytics/getting-started).
All signal processing runs on the user's device. Raw camera frames and raw
biosignals never need to leave the browser.
***
## What Elata Adds
Your app also gets Elata's platform features: sign-in, in-app purchases,
subscriptions, per-user storage, a cross-app biometric Score, and reminders.
See [Platform APIs](/apps/platform/host-bridge).
***
## Next
From upload to live
Start building
Open source apps built on the SDKs
# About the Archive
Source: https://docs.elata.bio/archive/overview
Earlier Elata projects and retired features, kept for reference.
The Archive holds earlier open-source projects from Elata. They're no longer
under active development, but their code is public and these pages are kept
for reference. For what's available today, see [What's live](/overview/status).
***
## Archived projects
| Project | What it was | Code |
| - | - | - |
| [AgentForge](/open-source/agentforge/overview) | Agent-based simulation framework for testing EVM smart contracts | [agentforge](https://github.com/Elata-Biosciences/agentforge) |
| [NewsReader](/open-source/newsreader/overview) | AI-assisted news scanner behind news.elata.bio | [elata-vsm-system-4](https://github.com/Elata-Biosciences/elata-vsm-system-4) |
| [ZORP](/zorp-protocol/zorp-intro) | On-chain research platform for encrypted data contribution; reached testnet in 2025 | [zorp](https://github.com/Elata-Biosciences/zorp) |
| [Elata EEG](/home/elata-eeg/elata-eeg-overview) | Open-source EEG hardware, up to 32 channels | [elata-eeg](https://github.com/Elata-Biosciences/elata-eeg) |
***
## Retired features
App tokens, staking, and governance are retired from the current product for now and are intended to return later. The future design and timing are not committed. Old links redirect to current guidance rather than implying these features are usable today.
| Retired | Today |
| - | - |
| Launching a token for each app, bonding curves, and trading app tokens | Apps are registered with a one-time fee; no token is launched |
| Staking for apps, tournaments, and veELTA pages | Not part of the live product |
| "Premium content" | [In-app purchases](/apps/platform/in-app-purchases) and [subscriptions](/apps/platform/subscriptions) |
| The API Reference tab | [Host bridge](/apps/platform/host-bridge) |
# Integrate your device with the Elata SDK
Source: https://docs.elata.bio/integrate
Integrate a headset or biosignal device into the Elata EEG BLE stack with a simple, reliable path.
This guide is for device companies that want their hardware to work with the
Elata SDK.
The target is simple: your device should plug into the shared Elata transport
boundary and emit stable frames that apps can trust.
Review the shared frame and transport types before writing adapter code.
Confirm Web Bluetooth, browser, and platform constraints first.
## What You Are Building
A production-ready integration should do three things well:
1. connect reliably
2. stream frames in a stable, predictable format
3. recover cleanly when the device, browser, or radio link misbehaves
In Elata terms, that means converging on `HeadbandTransport` and
`HeadbandFrameV1` instead of exposing vendor-specific packet layouts directly.
Apps should depend on the shared Elata transport contract, not on raw vendor
packets. That keeps downstream integrations simple and makes device support
easier to maintain.
## The Fast Path
Decide whether your device belongs upstream in `eeg-web-ble`, in a separate
package, or in an app-local adapter. Use
[Integration Paths](./integration-paths) to make that decision early.
Gather your GATT services, characteristics, packet format, sample rate,
channel names, timestamp behavior, and known firmware quirks. See
[Protocol Requirements](./protocol-requirements).
Build the device layer that handles discovery, session setup, packet
decoding, and frame emission. Start from
[Adapter Implementation](./adapter-implementation).
Test connect, start, stop, disconnect, malformed packets, reconnects, and
frame correctness before tuning anything downstream. Use
[Testing and Validation](./testing-and-validation).
Package the integration with platform notes, tested firmware versions, and
support expectations. See [Submission and Support](./submission-and-support).
## What You Need Before You Start
| Requirement | Why it matters |
| - | - |
| Device protocol details | You need a complete byte-level picture before implementation starts. |
| A supported browser path | Web Bluetooth requires Chrome or Edge in a secure context. |
| Stable channel metadata | Apps need consistent channel order, names, and sample rates. |
| A clear ownership model | Decide early whether this will live upstream, in a vendor package, or privately. |
If you have not reviewed the current BLE flow yet, read
[EEG BLE Getting Started](/sdk/eeg-web-ble/getting-started) first.
## Guide Map
Choose where the integration should live and how it should be owned.
The shared interfaces your device must satisfy.
The protocol and metadata inventory to collect before coding.
Build the device adapter and connect it to `BleTransport`.
Make the integration reliable under normal and failure conditions.
Hand off the integration with the docs and caveats partners need.
## Reliability First
A device integration is not done when it streams once on a happy path.
It is done when:
| Done means | Notes |
| - | - |
| `connect()` is predictable | discovery and session setup succeed or fail clearly |
| `start()` produces stable frames | row width, channel order, and sample rate stay correct |
| `stop()` is clean | the session stops without leaving the device in a bad state |
| `disconnect()` is clean | the browser session releases resources correctly |
| failures are surfaced clearly | apps can react to disconnects, retries, and unsupported states |
## Related Docs
* [Headband Transport](/sdk/eeg-web/headband-transport)
* [EEG BLE Getting Started](/sdk/eeg-web-ble/getting-started)
* [Muse Device](/sdk/eeg-web-ble/muse-device)
* [Compatibility](/sdk/operations/compatibility)
* [Troubleshooting](/sdk/operations/troubleshooting)
## Next
Pick the right ownership and packaging model before coding.
Confirm the frame and lifecycle expectations your adapter must satisfy.
# Integration Paths
Source: https://docs.elata.bio/integration-paths
Choose the right packaging and ownership model for a device integration.
Use this page before you write adapter code.
The right path depends on whether the integration is broadly useful, whether it
needs vendor SDKs or native bridges, and whether it will be maintained as a
public SDK surface.
## Recommended Default
If the device is likely to be useful to the wider Elata SDK audience, put it
upstream in `@elata-biosciences/eeg-web-ble`.
That keeps transport behavior centralized and avoids parallel adapters across
multiple apps.
## Choose By Outcome
| Path | Best when | Why |
| - | - | - |
| Upstream device module in `eeg-web-ble` | The device is broadly useful and works through browser BLE | Best default for public SDK support |
| Separate package under `packages/` | You need a vendor SDK, a license gate, or a localhost/native bridge | Keeps heavier integrations isolated |
| App-local adapter | You are proving a concept or shipping a private integration | Fastest path when no shared package is needed |
## Path 1: Upstream in `eeg-web-ble`
Choose this when:
* the device can work through Web Bluetooth in a browser
* the integration is likely to be reused by more than one app
* you want one shared implementation of transport behavior
This path usually means:
1. add a device module under the BLE package
2. reuse `BleTransport`
3. add mocked tests
4. update consumer docs and device notes
This is the best path for a public, durable integration that should feel like
a first-class part of the SDK.
## Path 2: Separate Package
Choose this when the device needs more than a clean browser BLE flow.
Typical reasons:
| Situation | Why a separate package helps |
| - | - |
| Licensed or heavy vendor SDK | Keeps extra dependencies out of the main BLE package |
| Localhost helper or native bridge | Lets you ship a bridge-specific transport cleanly |
| Independent release cadence | Lets the vendor or partner own versioning and support |
The package should still expose a `HeadbandTransport`, or a thin wrapper around
one, so app code stays consistent with the rest of the Elata stack.
## Path 3: App-Local Adapter
Choose this when speed matters more than reuse.
This is a good fit for:
* a private customer deployment
* a fast proof of concept
* evaluation work before a public package exists
Keep the contract the same even if the code stays private. That makes it easier
to move upstream later.
## Decision Order
If multiple apps would benefit, start by assuming an upstream path.
If Web Bluetooth will not work, or if a native bridge is required, move to a
separate package.
Shared support usually belongs upstream. Vendor-owned release lines often fit
better in a separate package.
No matter where the code lives, the app-facing contract should still be
`HeadbandTransport` and `HeadbandFrameV1`.
## Paths to Avoid
Do not choose an app-local adapter just because it is easy in the first hour.
That usually creates extra work later if the integration becomes public.
Do not push a heavy, bridge-based, or licensed integration into the shared BLE
package either. Keep the public package lean.
## Related Docs
* [EEG BLE Getting Started](/sdk/eeg-web-ble/getting-started)
* [Transport Contract](./transport-contract)
* [Adapter Implementation](./adapter-implementation)
* [Compatibility](/sdk/operations/compatibility)
## Next
Review the shared interfaces your chosen path must still satisfy.
Gather the protocol details you need before implementation begins.
# Core Concepts
Source: https://docs.elata.bio/open-source/agentforge/concepts
Scenarios, agents, ticks, packs, and determinism in AgentForge
## Scenarios
A scenario is the top-level configuration for a simulation run. It defines:
* **`name`** — identifier for the scenario
* **`seed`** — deterministic RNG seed (same seed = same results)
* **`ticks`** — number of simulation steps
* **`tickSeconds`** — simulated time per tick (e.g., 3600 = 1 hour)
* **`pack`** — protocol adapter that sets up world state
* **`agents`** — list of agent types, counts, and parameters
* **`assertions`** — post-run validation checks
* **`gossip`** — channel definitions, budgets, and trust settings
```typescript theme={null}
import { defineScenario } from '@elata-biosciences/agentforge';
export default defineScenario({
name: 'my-scenario',
seed: 42,
ticks: 200,
tickSeconds: 3600,
pack: myPack,
agents: [
{ type: MyAgent, count: 10 },
],
assertions: [
{ type: 'gt', metric: 'totalVolume', value: 0 },
],
});
```
***
## Ticks
A tick is one simulation step. During each tick:
1. The engine calls `step(ctx)` on every agent in order
2. Each agent returns zero, one, or up to three actions
3. The pack executes each action against the protocol state
4. Metrics, gossip, and artifacts are recorded
Tick ordering can be configured: **priority-based** (default), **random**, or **custom**. The ordering policy affects outcomes — see the mechanism experiments for analysis.
***
## Agents
Agents are autonomous actors. Every agent extends `BaseAgent` and implements `step()`:
```typescript theme={null}
export class MyAgent extends BaseAgent {
async step(ctx: TickContext): Promise {
// Observe state, decide, and return actions
}
}
```
### Agent Context
Each tick, agents receive a `TickContext` with:
| Property | Description |
| - | - |
| `ctx.rng` | Deterministic random number generator |
| `ctx.world` | Current protocol state |
| `ctx.gossip` | Read and post gossip messages |
| `ctx.capabilities` | Available contracts, tools, and action templates |
| `ctx.tick` | Current tick number |
| `ctx.tickSeconds` | Simulated seconds per tick |
### Agent State
Agents can persist state across ticks:
* **`this.remember(key, value)`** — store a value
* **`this.recall(key)`** — retrieve a stored value
* **`this.setCooldown(name, ticks)`** — rate-limit an action
* **`this.isOnCooldown(name)`** — check cooldown status
### Multi-Action Support
Agents can return an array of up to 3 actions per tick. The engine executes each action in order within the same tick:
```typescript theme={null}
return [
{ id: this.generateActionId('swap', ctx.tick), name: 'swap', params: { ... } },
{ id: this.generateActionId('post', ctx.tick), name: 'PostMessage', params: { ... } },
];
```
### Persona LLM Agents
`PersonaLlmAgentBase` provides a reusable base for LLM-driven agents with:
* Persona profile (id, style, goals, risk profile, tool preferences)
* Structured prompt assembly with full capability manifest context
* Two-stage OODA loop: plan then act, with fallback to single-shot parsing
* Schema-validated action intents via Zod
***
## Packs
Packs are protocol adapters. They bridge AgentForge to your smart contracts by implementing the `Pack` interface:
* **`setup()`** — deploy contracts, initialize state
* **`executeAction(action)`** — execute an agent's action against the protocol
* **`getWorldState()`** — return current protocol state for agents to observe
* **`getCapabilityManifest()`** — describe available contracts, functions, and action templates
The built-in `ToyPack` provides a simple price-simulation environment for getting started.
***
## Gossip
The gossip bus enables inter-agent communication:
* **Channels** — global broadcast or scoped strategy channels with membership lists
* **Free-form messages** — text payloads with optional `credibilityPrior` scores (0-1)
* **Budgets** — configurable `maxPostsPerTick` and `maxReadsPerTick` per agent
* **Cooldowns** — per-channel `postCooldownTicks` to rate-limit posting
```typescript theme={null}
gossip: {
channels: [
{ id: 'global', members: 'all' },
{ id: 'strategy', members: ['LlmAgent-0', 'LlmAgent-1'] },
],
budgets: { maxPostsPerTick: 3, maxReadsPerTick: 10 },
}
```
Strategy channels are useful for LLM-only coordination without flooding deterministic agents.
***
## Determinism
Same seed + same scenario = identical results. All randomness flows through a seeded RNG. This enables:
* **Reproducible CI** — assertions pass or fail consistently
* **Replay** — re-run exploration traces deterministically against updated contracts
* **Comparison** — diff two runs with `forge-sim compare`
```bash theme={null}
forge-sim run --toy --seed 123 --out run1 --ci
forge-sim run --toy --seed 123 --out run2 --ci
forge-sim compare run1/toy-market-ci run2/toy-market-ci
```
***
## Exploration / Replay Workflow
1. **Explore** — run with LLM agents in exploration mode to discover behaviors
2. **Capture** — a `replay_bundle.json` records the full decision trace
3. **Replay** — re-run the exact same trace deterministically against updated contracts
```bash theme={null}
forge-sim run scenario.ts --mode exploration
forge-sim run scenario.ts --mode replay --replay-bundle results/run/replay_bundle.json
```
This allows you to discover emergent attack vectors with LLMs, then regression-test them after contract changes.
# AgentForge Overview
Source: https://docs.elata.bio/open-source/agentforge/overview
Adversarial, agent-based simulation of EVM mechanisms
## What is AgentForge?
AgentForge is an agent-based simulation framework for Foundry and EVM smart contracts. It fills the gap between isolated unit tests and production by simulating how a protocol behaves when many autonomous agents — traders, arbitrageurs, liquidators, LLM-driven strategists — act simultaneously with competing strategies over time.
**Package**: [`@elata-biosciences/agentforge`](https://www.npmjs.com/package/@elata-biosciences/agentforge) on npm
| Layer | What it tests | Example |
| - | - | - |
| Unit tests | Individual functions | `test_transfer()` |
| Fuzz tests | Random inputs | `testFuzz_transfer(uint256 amount)` |
| **AgentForge** | **Multi-actor emergent behavior** | **Traders, arbitrageurs, liquidators competing** |
| Mainnet | Real users | Production |
***
## Key Features
* **Deterministic simulation** — same seed + same scenario = identical results
* **Multi-action agents** — agents can execute up to 3 actions per tick (e.g., trade + gossip)
* **LLM-driven agents** — persona-based agents with structured prompt assembly and OODA loop
* **Gossip bus** — bounded inter-agent communication with channels, budgets, and trust primitives
* **Exploration/Replay workflow** — discover behaviors with LLM agents, then replay deterministically
* **Studio dashboard** — Bloomberg Terminal-inspired UI with charts, timeline, gossip analysis, and ML toolkit
* **Protocol adapters (Packs)** — plug into any EVM protocol via the `Pack` interface
* **CI-ready** — exit codes, stable artifact naming, and GitHub Actions integration
***
## Quick Start
```bash theme={null}
# Install
pnpm add @elata-biosciences/agentforge
# Scaffold a simulation folder
npx forge-sim init
# Run the built-in toy scenario
npx forge-sim run --toy
# Launch the Studio dashboard
npx forge-sim studio
```
Requirements: **Node.js 18+** and optionally [Foundry](https://book.getfoundry.sh/getting-started/installation) with Anvil for EVM simulations.
***
## Writing Your First Agent
Define an agent that observes state and decides actions each tick:
```typescript theme={null}
import { BaseAgent, type Action, type TickContext } from '@elata-biosciences/agentforge';
export class MyTrader extends BaseAgent {
async step(ctx: TickContext): Promise {
if (ctx.rng.chance(0.3)) {
return {
id: this.generateActionId('buy', ctx.tick),
name: 'buy',
params: { amount: ctx.rng.nextInt(1, 100), asset: 'TOKEN' },
};
}
return null;
}
}
```
Define a scenario that wires agents together:
```typescript theme={null}
import { defineScenario } from '@elata-biosciences/agentforge';
import { ToyPack, RandomTraderAgent, MomentumAgent } from '@elata-biosciences/agentforge/toy';
export default defineScenario({
name: 'market-stress',
seed: 42,
ticks: 100,
tickSeconds: 3600,
pack: new ToyPack({
assets: [{ name: 'TOKEN', initialPrice: 100, volatility: 0.05 }],
initialCash: 10000,
}),
agents: [
{ type: RandomTraderAgent, count: 10 },
{ type: MomentumAgent, count: 5, params: { threshold: 0.02 } },
],
assertions: [
{ type: 'gt', metric: 'totalVolume', value: 0 },
{ type: 'gte', metric: 'successRate', value: 0.9 },
],
});
```
Run it:
```bash theme={null}
npx forge-sim run sim/scenarios/market-stress.ts --seed 42
```
***
## Run Artifacts
Every run produces durable artifacts:
```
results/market-stress-ci/
├── summary.json # Run metadata, final metrics, assertion results
├── metrics.csv # Time-series data for analysis
├── actions.ndjson # Complete action log
├── gossip.ndjson # Gossip posts/deliveries
├── config_resolved.json # Resolved configuration for reproducibility
├── replay_bundle.json # Exploration trace for replay mode
└── report.md # Generated report
```
***
## Demo Integrations
* [Uniswap v4 Fork Demo](https://github.com/wkyleg/v4-core/tree/main/sim/agentforge-demo) — deterministic + LLM agents simulating v4 pool mechanisms
* [Elata Protocol](https://github.com/Elata-Biosciences/elata-protocol) — app launchpad with bonding curves
***
## Links
* [npm package](https://www.npmjs.com/package/@elata-biosciences/agentforge)
* [GitHub repository](https://github.com/Elata-Biosciences/agentforge)
* [Full CLI Reference](/open-source/agentforge/cli)
* [Core Concepts](/open-source/agentforge/concepts)
* [Studio Dashboard](/open-source/agentforge/studio)
# Apps on Elata
Source: https://docs.elata.bio/overview/apps-on-elata
What kinds of apps live on Elata, how they're reviewed, and how they run safely in your browser.
## What an Elata app is
An Elata app is a web app that responds to signals from your body: your pulse
from a webcam, brain activity from an EEG headband, or heart rate from a
headband's optical sensor. You open it at [app.elata.bio](https://app.elata.bio)
and play it in the browser. There's nothing to install.
***
## Kinds of apps
| Category | Examples |
| - | - |
| **Games** | Games that react to your heart rate, focus, or calm |
| **Neurometrics** | Live EEG and heart-rate dashboards |
| **Wellness** | Breathwork, meditation, and recovery |
| **Tools** | Focus timers and self-tracking |
| **Experiments** | Studies, prototypes, and research apps |
***
## How apps get onto Elata
Anyone can build an app with the open-source [Elata SDK](/overview/elata-sdk).
To publish it, a builder registers the app on Elata, uploads the build, and
submits it for review.
Every release goes through:
1. **Automatic checks** when it's uploaded: the right file layout, size limits, and use of the Elata SDK.
2. **An automated security scan** of the code.
3. **A human moderator review** against the [submission policy](/apps/build/review-and-policy).
Only approved releases are public. Builders can share a draft with a few
playtesters before that.
***
## How apps run safely
Each app runs in an **isolated sandbox** on its own web origin. That means:
* Elata's bridge does not disclose your account email, wallet keys, or session tokens to the embedded app. Public activity and payment records can still be identifying.
* It can ask for your camera or Bluetooth, and your browser asks you first.
* When it needs something from Elata, like a purchase or saved progress, it sends a request and Elata handles it on your behalf.
* Payment details never pass through the app.
***
## What builders get
Builders don't have to build accounts, payments, hosting, or storage. Elata
provides:
| Capability | Status |
| - | - |
| Hosting, listing, reviews, and discovery | Live |
| Saved progress and scores per user | Live |
| Reminders through Elata | Live |
| Contributing to the user's opt-in biometric Score | Live |
| One-time in-app purchases | Implemented on the IAP overhaul branch; deployment and payment configuration must be verified |
| Monthly subscriptions | Experimental |
See [What's live](/overview/status) for details, and the
[Build Apps](/apps/what-are-elata-apps) tab to get started.
# Biosignals Explained
Source: https://docs.elata.bio/overview/biosignals
A plain-language guide to camera heart rate, EEG headbands, and headband PPG: what each measures and what it can't tell you.
A **biosignal** is a measurable signal from the body, like a heartbeat or the
brain's electrical activity. Elata apps use three kinds, all from devices
people already own or can buy.
***
## Camera heart rate (rPPG)
**What it is.** Each heartbeat pushes blood into the skin, which changes its
color very slightly. A webcam can pick up that change, too small to see with
your eyes. This is called remote photoplethysmography, or rPPG.
**What it can estimate.** Pulse rate, and how steady the signal is.
**What affects it.** Movement, dim or flickering light, strong shadows, video
compression, and low frame rates all reduce accuracy. Accuracy can also vary
with skin tone and camera quality.
More detail: [Camera heart rate (rPPG)](/overview/science/rppg).
***
## EEG headbands
**What it is.** Electroencephalography (EEG) measures tiny electrical
signals from the brain through sensors on the scalp. Consumer headbands like
Muse use a few dry sensors on the forehead and behind the ears.
**What it can estimate.** How strong different rhythms are, such as alpha
waves, which tend to rise when you're relaxed with your eyes closed. Apps turn
these into measures like "calm" or "focus."
**What affects it.** Blinking, jaw clenching, movement, and poor sensor contact
create signals much larger than brain activity. A few sensors can't locate
activity precisely, and "calm" or "focus" scores are estimates, not direct
readouts of a mental state.
More detail: [EEG headbands](/overview/science/eeg).
***
## Headband PPG and heart-rate variability
**What it is.** Some headbands, like Muse S, have an optical sensor that
shines light into the skin and measures the reflection, the same principle as
a smartwatch heart-rate sensor (photoplethysmography, or PPG).
**What it can estimate.** Heart rate and **heart-rate variability (HRV)**: the
small variation in time between beats, which is often linked to recovery and
stress.
**What affects it.** Movement, a loose fit, and short recordings. HRV in
particular needs clean data over a minute or more to be meaningful.
More detail: [PPG and HRV](/overview/science/ppg-and-hrv).
***
## What these signals are not
Elata apps are not medical devices. They don't diagnose, treat, or monitor
any medical condition. If an app shows a reading that worries you, talk to a
medical professional rather than relying on the app.
These measurements are useful for games, feedback, and personal exploration.
They are estimates that depend on conditions, and the same person can get
different readings on different days.
***
## Where processing happens
All three kinds of signal are processed on your device by the
[Elata SDK](/overview/elata-sdk). Raw camera frames and raw biosignals don't
need to leave your device. See [Privacy and consent](/overview/privacy-and-consent).
# Community
Source: https://docs.elata.bio/overview/community
Where to find the Elata community, how to give feedback, and how to contribute.
## Join the conversation
Chat with builders, users, and the Elata team
Announcements and updates
The Elata channel
Research and industry news, curated for the community
***
## Give feedback
* **About an app:** leave a review or a comment on its page on Elata, or use the app's own feedback button if it has one.
* **About Elata itself:** use the feedback button on [app.elata.bio](https://app.elata.bio), or tell us on Discord.
* **Report a problem with an app:** see [Help](/users/help).
* **Email:** [info@elata.bio](mailto:info@elata.bio)
***
## Contribute
The Elata SDK is open source. You can:
* Report bugs and request features in [GitHub issues](https://github.com/Elata-Biosciences/elata-bio-sdk/issues)
* Add support for a new headset by following [Device integration](/integrate)
* Improve these docs: the [docs repository](https://github.com/Elata-Biosciences/docs) is public
Other public projects are on [Elata's GitHub](https://github.com/Elata-Biosciences).
***
## Earn points for taking part
Reviews, comments, sharing, and inviting friends all earn
[Elata Points](/overview/points).
***
## Brand and media
Logos and brand assets are on the [Media assets](/resources/media-assets) page.
# How Elata Fits Together
Source: https://docs.elata.bio/overview/ecosystem-map
The ecosystem map: devices, the Elata SDK, apps, and Elata itself, and how data and value move between them.
## The big picture
```mermaid theme={null}
graph LR
D[Devices webcam · EEG headband] --> S[Elata SDK runs on the user's device]
S --> A[Biosignal apps]
A -->|sandboxed bridge| E[Elata accounts · payments · storage · consent]
E --> U[Users]
U --> A
E -.-> P[Points and the biometric Score across all apps]
```
Elata has four layers. Each one has its own section in these docs.
***
## 1. Devices
The sensors are things people already own:
* **A webcam** can estimate pulse from tiny color changes in the skin (remote photoplethysmography, or rPPG).
* **A consumer EEG headband**, such as Muse 2 or Muse S, measures electrical activity from a few points on the scalp, and some models also have an optical (PPG) heart-rate sensor.
See [Devices you can use](/users/devices) and, for the science,
[Biosignals explained](/overview/biosignals).
***
## 2. The Elata SDK
The [Elata SDK](/overview/elata-sdk) is a set of open-source packages that
connect to those devices and turn raw signals into usable measures such as heart
rate, heart-rate variability, and EEG band powers. Processing runs in the
browser, on the user's device, using WebAssembly. Builders use the SDK so they
don't have to write signal processing from scratch.
***
## 3. Apps
Builders combine the SDK with their own ideas: games you steer with focus or
calm, breathwork guides that follow your heart rate, live dashboards, research
tools. Apps are ordinary web apps. See [Apps on Elata](/overview/apps-on-elata).
***
## 4. Elata
[Elata](https://app.elata.bio) is where those apps live. It:
* **Reviews** every release (an automated security scan plus a human moderator)
* **Hosts** each app in an isolated sandbox, so an app can't see who the user is or touch their account
* **Provides** sign-in, purchases, subscriptions, and saved progress, so builders don't have to
* **Asks for consent** in its own interface before a derived session result contributes to the platform biometric Score
***
## Across all apps
Two things span every app on Elata:
* **[Elata Points](/overview/points)** reward taking part: playing, reviewing, building, and inviting friends.
* **The biometric Score** is an optional, cross-app measure built only from derived session results, and only if the user opts in. See [Privacy and consent](/overview/privacy-and-consent).
***
## How value moves
| Who | Gives | Gets |
| - | - | - |
| Users | Attention, feedback, optional purchases | Apps that respond to their body, with privacy built in |
| Builders | Apps | Distribution, built-in accounts and payments, and revenue from purchases and subscriptions |
| Elata | The platform and the SDK | A one-time registration fee per app and a platform fee on sales and subscriptions |
Details: [Monetization](/overview/monetization).
***
## Where data lives
| Data | Where it stays |
| - | - |
| Raw camera frames and biosignals | On the user's device |
| Saves, settings, and scores an app stores | The user's account, scoped to that one app |
| Derived session results for the Score | Shared with Elata only with the user's consent |
| What builders see | Aggregate usage analytics; Commerce reports can also show shortened buyer wallets and transaction links |
# The Elata SDK
Source: https://docs.elata.bio/overview/elata-sdk
Open-source packages that turn webcams and consumer headbands into biosignal sensors, with processing on the user's device.
## What it does
The Elata SDK is a set of open-source JavaScript packages for building apps on
biosignals. It handles the hard parts:
* **Connecting** to devices: a webcam, or a Bluetooth EEG headband such as Muse
* **Processing** raw signals into usable measures: heart rate, heart-rate variability, EEG band powers, signal quality
* **Running on the device**: the processing core is written in Rust and compiled to WebAssembly, so it runs in the browser and raw data doesn't have to go to a server
The SDK is free to use, published on [npm](https://www.npmjs.com/org/elata-biosciences),
and developed in the open on
[GitHub](https://github.com/Elata-Biosciences/elata-bio-sdk).
***
## Package families
| Family | Packages | Status |
| - | - | - |
| **Sensors** | `rppg-web` (webcam heart rate), `eeg-web` (EEG processing), `eeg-web-ble` (Bluetooth headbands), `ppg-web` (headband heart rate) | Live |
| **Elata platform** | `app-payments` (purchases), `app-metrics` (records and scores) | Live |
| | `app-state` (saved progress) | Experimental |
| **Recording and analysis** | `biosignal-session` (local session recording), `biosignal-analytics` (features and scores), `rppg-models-web` (learned waveform models) | Experimental |
| **Tooling** | `create-elata-demo` (starter apps) | Live, known issue |
"Experimental" packages are available as source code but not yet on npm. See
[What's live](/overview/status).
***
## Supported devices
| Device | Signals | Browser |
| - | - | - |
| Any webcam | Pulse (rPPG) | Modern browsers |
| Muse 2 and Muse S | 4-channel EEG, PPG | Chrome or Edge (Web Bluetooth) |
| Muse S (Athena) | 8-channel EEG, optics, motion | Chrome or Edge (Web Bluetooth) |
Other headsets can be added through the SDK's device contract. See
[Device integration](/integrate).
***
## You don't have to publish on Elata
The SDK works in any web app. Publishing on Elata adds distribution, accounts,
and payments, but the sensor packages don't require it.
Packages, installation, and device support
A hands-on tutorial
# ELTA Token
Source: https://docs.elata.bio/overview/elta-token
The facts about ELTA: what it is, where it's deployed, and how to verify the official contract.
## What ELTA is
ELTA is the ERC-20 token for the Elata ecosystem. It has a fixed supply of
77,000,000 tokens, minted once when the contract was deployed.
**No official liquidity pool exists yet.** Any pool or listing that appears
before an official announcement from Elata is unauthorized. Always check that
a contract address matches the one below exactly before interacting with it.
***
## Official addresses
| Network | Address | Status |
| - | - | - |
| Base mainnet (canonical) | `0x6335495c88D1D78245BB50f321E4e450f865CfD0` ([BaseScan](https://basescan.org/address/0x6335495c88D1D78245BB50f321E4e450f865CfD0)) | Live |
| Ethereum Sepolia (testnet) | `0x2AEb03A678A1e1E99a2AeEb4CeFCD0263A2D3587` ([Etherscan](https://sepolia.etherscan.io/address/0x2AEb03A678A1e1E99a2AeEb4CeFCD0263A2D3587)) | Testnet only |
The canonical list of official addresses is maintained in the public
[ELTA repository](https://github.com/Elata-Biosciences/ELTA/blob/main/OFFICIAL.md).
***
## Token facts
| Property | Value |
| - | - |
| Name and symbol | Elata / ELTA |
| Decimals | 18 |
| Supply | 77,000,000, fixed. There is no way to mint more. |
| Admin controls | None: no pause, upgrade, blacklist, or transfer fee |
| Audit | [Distrust LLC](https://distrust.co), March 2026: 0 critical, high, medium, or low findings; 7 informational ([report](https://github.com/Elata-Biosciences/ELTA/tree/main/audits)) |
***
## ELTA and the Elata apps
You don't need ELTA to use Elata. Browsing and playing apps, earning
[Elata Points](/overview/points), and publishing apps don't require holding or
spending ELTA. Elata Points are separate from ELTA: they're off-chain and
can't be bought, sold, or transferred.
Elata's wider on-chain protocol, including staking and governance, is not part
of the live product. See [What's live](/overview/status).
This page is factual information, not financial advice or an offer of any
kind.
***
## Sources
* [ELTA repository and README](https://github.com/Elata-Biosciences/ELTA)
* [Official addresses](https://github.com/Elata-Biosciences/ELTA/blob/main/OFFICIAL.md)
# Find Your Path
Source: https://docs.elata.bio/overview/find-your-path
Suggested reading for users, builders, researchers, partners and investors, and community members.
## I want to use Elata apps
1. [Get started](/users/get-started): open Elata, sign in, and try your first app
2. [Devices you can use](/users/devices): a webcam is enough, but you can use other devices too
3. [Your data](/users/your-data): what stays on your device and what you control
4. [Safety and limitations](/users/safety-and-limitations): what these measurements can and can't tell you
***
## I want to build an app
1. [Apps on Elata](/overview/apps-on-elata): what kinds of apps live here
2. [Quickstart](/apps/quickstart): scaffold, build, and submit your first app
3. [Elata SDK](/sdk/overview): the packages for camera, EEG, and PPG signals
4. [Monetization](/overview/monetization): how builders earn
***
## I'm a researcher
1. [Science overview](/overview/science/overview): Elata's scientific stance
2. [Camera heart rate](/overview/science/rppg) and [EEG headbands](/overview/science/eeg): how they work and their limits
3. [Metrics and scores](/overview/science/metrics-and-scores): how derived measures are built
4. [Data for research](/overview/science/data-for-research): what data exists and what doesn't
***
## I'm a partner
1. [What's live](/overview/status): current status of every capability
2. [How Elata fits together](/overview/ecosystem-map): the components and how value moves
3. [Mission and history](/overview/mission-and-history): where Elata came from
4. [Partners](/overview/partners): how to work with us
***
## I make a headset or sensor
1. [Device integration](/integrate): bring your device to the Elata SDK
2. [Transport contract](/transport-contract): the interface your device implements
3. [Submission and support](/submission-and-support): getting your integration listed
***
## I want to join the community
1. [Community](/overview/community): Discord, X, Farcaster, and GitHub
2. [Elata Points](/overview/points): earn points for taking part
3. [Contribute to the SDK](https://github.com/Elata-Biosciences/elata-bio-sdk): Elata SDK is open source
# Glossary
Source: https://docs.elata.bio/overview/glossary
Plain-language definitions of the terms used across the Elata docs.
**Alpha waves.** A rhythm in EEG between about 8 and 12 Hz, often stronger when
a person is relaxed with their eyes closed.
**App state.** Small pieces of data, such as saves and settings, that an app
stores for a user through Elata. Scoped to that app and that user.
**Artifact.** In EEG, a signal that doesn't come from the brain, such as a
blink, jaw clench, or loose sensor. Artifacts are usually much larger than
brain activity.
**Band power.** How much of an EEG signal falls in a frequency band, such as
alpha or beta. The basis of most "calm" and "focus" measures.
**Biometric Score.** An optional, cross-app measure (0 to 100) built from
derived session results, only with the user's consent. See
[Privacy and consent](/overview/privacy-and-consent).
**Biosignal.** A measurable signal from the body, such as a heartbeat or the
brain's electrical activity.
**Consent.** A user's explicit, revocable permission for an app to contribute
derived results to their Score. Requested by Elata, not by the app.
**Derived result.** A summary computed from raw signals, such as average heart
rate over a session. Raw signals are never part of it.
**EEG (electroencephalography).** Measuring the brain's electrical activity
through sensors on the scalp.
**Elata.** The marketplace at [app.elata.bio](https://app.elata.bio) where
biosignal apps are published and played.
**Elata Points.** Off-chain, non-transferable points earned for taking part on
Elata. See [Points](/overview/points).
**Elata SDK.** Open-source packages for building biosignal apps. See
[The Elata SDK](/overview/elata-sdk).
**ELTA.** The ERC-20 token for the Elata ecosystem, on Base. See
[ELTA token](/overview/elta-token).
**Federated learning.** Training a model across many devices without
collecting their raw data in one place. Planned research direction for Elata.
**Host bridge.** The set of messages an app uses to ask Elata for things like
purchases and saved progress. See [Host bridge](/apps/platform/host-bridge).
**HRV (heart-rate variability).** The variation in time between heartbeats.
Often linked to recovery and stress, and sensitive to recording conditions.
**[Muse](choosemuse.com).** A consumer EEG headband line (Muse 2, Muse S, and the newer Muse S Athena) supported by the Elata SDK.
**PPG (photoplethysmography).** Measuring pulse by shining light into the skin
and detecting changes in the reflected light, as smartwatches do.
**Release.** One version of an app on Elata. Every release is scanned and
reviewed before it goes public.
**rPPG (remote photoplethysmography).** Estimating pulse from a camera by
detecting tiny color changes in the skin.
**Sandbox.** The isolated environment each app runs in, on its own web origin,
so it can't see who the user is or access their account.
**Web Bluetooth.** A browser feature that lets web pages connect to Bluetooth
devices. Available in Chrome and Edge, not in Safari or iOS browsers.
**WebAssembly (WASM).** A format that lets fast, compiled code run in the
browser. The Elata SDK's signal processing runs as WebAssembly.
# Mission and History
Source: https://docs.elata.bio/overview/mission-and-history
Why Elata exists, and the public milestones that got it here.
## Mission
Elata's goal is to make biosignal technology open and accessible: a
marketplace for privacy-respecting biometric apps that bring personal insight
to anyone, using devices they already have.
Three principles run through everything Elata builds:
* **Open.** The [Elata SDK](/overview/elata-sdk) and many earlier projects are open source.
* **Private by design.** Raw signals are processed on the user's device. Platform Score contributions are opt-in; app saves, event records, scores, and payments have separate storage paths.
* **Accessible.** Many apps need nothing more than a webcam, and consumer headbands cost a fraction of clinical equipment.
In an [October 2025 interview](https://finance.yahoo.com/news/exclusive-either-control-minds-hand-134608461.html),
co-founder Andreas Melhede described the aim as democratizing brain-computer
interface technology, "by the people, for the people."
***
## How Elata got here
The milestones below are history. For what's available today, see
[What's live](/overview/status).
| When | Milestone | Today |
| - | - | - |
| 2025 | **ElataEEG**: open-source EEG hardware, upgraded from 8 to 32 channels with lower noise | [Archived](/home/elata-eeg/elata-eeg-overview) |
| 2025 | **ZORP**: an on-chain research platform for encrypted data contribution and participant compensation, which reached testnet | [Archived](/zorp-protocol/zorp-intro) |
| 2025 | **Brain Pong at Token2049 Singapore**: a tournament where players controlled a game with EEG headsets, covered by WIRED | Event |
| March 2026 | **Elata SDK** published on npm: browser packages for webcam heart rate, EEG headbands, and headband PPG | [Live](/overview/elata-sdk) |
| 2026 | **Elata** at app.elata.bio: a marketplace where builders publish biosignal apps and users play them in the browser | [Live](/overview/apps-on-elata) |
| May 2026 | **ELTA token** deployed on Base after an external audit (March 2026); no official liquidity pool yet | [Live](/overview/elta-token) |
Elata has also released other open-source tools along the way, including
**AgentForge** (agent-based simulation for EVM smart contracts) and the
**NewsReader** that powers news.elata.bio. They're described in the
[Archive](/archive/overview).
***
## Press
* [Yahoo Finance: interview with Elata's founder](https://finance.yahoo.com/news/exclusive-either-control-minds-hand-134608461.html) (October 2025)
* [Cointelegraph: "Decentralized science will bring the brain onchain"](https://www.tradingview.com/news/cointelegraph:f89b78615094b:0-decentralized-science-will-bring-the-brain-onchain/), an opinion piece by co-founder Andreas Melhede
* [WIRED](https://www.wired.com/story/expired-tired-wired-wearables/)
More on [elata.bio](https://elata.bio) and the [Elata news site](https://news.elata.bio/).
***
## Sources
* [Elata 2025 year in review](https://elata.bio/blog/2025-year-in-review-elata)
* [elata.bio](https://elata.bio)
* [ELTA repository](https://github.com/Elata-Biosciences/ELTA)
# Commercialization
Source: https://docs.elata.bio/overview/monetization
What users pay for, how builders earn, and where payments go.
Elata lets builders sell one-time items and monthly app access through the platform's checkout. Users manage their payments in **Settings → Billing**; builders manage their app's monetization in **Edit → Commerce**.
Availability depends on the deployment and payment configuration. See [What's live](/overview/status). The app purchase and subscription flow described here settles in USDC on Base when enabled. A preview can use real funds; check the network shown in checkout.
## What users can pay for
| Purchase | What it provides | Where to manage it |
| - | - | - |
| In-app item | A one-time unlock inside one app | Settings → Billing → Purchases |
| App subscription | Access to one subscription-gated app for the paid period | Settings → Billing → Subscriptions |
| Credits | Access to supported metered features | Settings → Billing → Wallet |
App subscriptions renew manually. Selecting an app for next month does not pay for it: complete checkout to secure access.
Credits and [Elata Points](/overview/points) are separate from the USDC used to buy items and app subscriptions. App subscription access does not grant ownership of every in-app item.
See [Billing and purchases](/users/billing-and-purchases) for checkout, access, and billing instructions.
## How builders earn
Builders create products and subscription plans in their app's **Commerce** section. One-time items use [`app-payments`](/apps/platform/in-app-purchases); whole-app subscriptions require no purchase code inside the app.
For direct app sales, checkout routes USDC through the payment splitter. The splitter deducts the configured platform fee and sends the creator's share to the app's payout wallet. There is no separate Elata creator balance to withdraw later.
The **Revenue** report shows gross sales before the split. The **Funds** card shows the payout wallet's current USDC balance on Base. These are different numbers: wallet transfers, fees, and other wallet activity affect the balance.
Start with [Monetize your app](/apps/build/monetize), then follow [Revenue and payouts](/apps/operate/revenue-and-payouts) to access and move your funds.
## Fees
| Cost | When it applies |
| - | - |
| Registration fee | Once per app, at registration; the wizard shows the amount |
| Platform fee | Deducted by the splitter from direct app payments |
| Network or payment-provider fees | As applicable to checkout or transfers |
## App tokens
Buying app items or subscription access does not require ELTA or an app token. The earlier app-token launch model is [retired from the current product for now](/overview/status#retired-from-the-current-product). Protocol features are intended to return later, with no committed timing or final design.
# Partners
Source: https://docs.elata.bio/overview/partners
What Elata is today, and how device makers, researchers, and app studios can work with us.
## Elata today
Elata is two things that are live now:
* **[Elata](/overview/apps-on-elata)**, a marketplace at app.elata.bio where builders publish biosignal apps and users play them in the browser
* **The [Elata SDK](/overview/elata-sdk)**, open-source browser packages for webcam heart rate, EEG headbands, and headband PPG
Some features are experimental and some are planned. The
[What's live](/overview/status) page lists every capability with its status, so
you can see exactly what exists today.
***
## Ways to work with Elata
| You are | Start here | What it looks like |
| - | - | - |
| **A device maker** | [Device integration](/integrate) | Implement the SDK's transport contract so your headset works in every Elata app |
| **A researcher** | [Science](/overview/science/overview) | Build studies as Elata apps, or use the open-source SDK in your own tools |
| **An app studio or developer** | [Build apps](/apps/what-are-elata-apps) | Publish on Elata and use its built-in accounts and payments |
| **An investor or ecosystem partner** | [How Elata fits together](/overview/ecosystem-map) | Understand the components, then get in touch |
***
## Get in touch
Email [info@elata.bio](mailto:info@elata.bio) with a short note about who you
are and what you have in mind.
***
## Resources
* [Mission and history](/overview/mission-and-history), including press coverage
* [Media assets](/resources/media-assets): logos and brand files
* [ELTA token](/overview/elta-token): official facts and addresses
* [Elata on GitHub](https://github.com/Elata-Biosciences)
These docs describe what exists today. They are not an offer of securities
or financial advice, and they don't make forward-looking commitments.
# Elata Points
Source: https://docs.elata.bio/overview/points
Non-transferable points you earn for using, building, and growing Elata.
## What points are
Elata Points reward real participation in Elata: playing apps,
writing reviews, building, and bringing friends. Points are tied to your
account. They can't be bought, sold, or transferred.
See your balance, history, and the leaderboard at
[app.elata.bio/points](https://app.elata.bio/points).
***
## How to earn
| Activity | Examples |
| - | - |
| **Showing up** | Daily sign-in, and bonuses for 3-, 7-, and 30-day streaks |
| **Playing** | Playing apps, your first app played, weekly quests |
| **Community** | Writing reviews, commenting, voting, following, saving apps |
| **Sharing** | Sharing apps, and clicks on your shared links |
| **Referrals** | A friend you invited joins and is verified |
| **Profile** | Completing your profile, verifying your email, adding social links |
| **Building** | Publishing an app, and your app reaching 100 and 1,000 plays |
| **Purchases** | Buying in-app items |
Most activities have a daily cap, and some can only be earned once. Point
values and caps are set by Elata and may change; the Points page shows the
current values.
Referral points are only granted for genuine, verified sign-ups. Activity
that looks like farming may be reversed.
***
## Quests
Quests are short goals with a points reward, such as playing apps on two
different days in a week. Your progress is tracked automatically. Claim the
reward from the Points page once it's complete.
***
## Leaderboard and seasons
The leaderboard ranks users by points earned over the week or month. Points
are also grouped into seasons; a season's ranking doesn't change your lifetime
total.
***
## Spending points
You can spend points on profile badges and titles, such as **Supporter** and
**Pioneer**, from the Points page.
***
## FAQ
No. Points can only be earned through activity on Elata.
The activity may have hit its daily cap, or it may be a one-time reward you've already earned. Some rewards, like referrals, are granted after a check that can take up to a day.
Yes. Publishing an app and reaching play milestones both earn points.
# Privacy and Consent
Source: https://docs.elata.bio/overview/privacy-and-consent
What stays on your device, what apps can store, how the opt-in biometric Score works, and your rights over your data.
## On-device first
The Elata SDK processes biosignals on your device. Raw camera frames, raw EEG,
and raw PPG don't need to leave it, and Elata's
[submission policy](/apps/build/review-and-policy) forbids apps from sending
them anywhere.
Elata's [privacy policy](https://elata.bio/privacy) puts it this way: raw
physiological signals never leave the user's device through the SDK, and the
processing pipeline is designed to produce derived metrics instead of transmitting raw signals. A derived value can still be personal data when associated with an account or transaction.
***
## What apps can see
The hosted app runs in a sandbox, and Elata's bridge does not give it your account email, wallet keys, or session tokens. When an app needs something from the platform, Elata answers on your behalf. This isolation does not make every interaction anonymous: public profile activity, purchase records, transaction hashes, or information you choose to enter may identify you.
***
## What apps can store
An app can save data for you through Elata:
* settings, saves, and progress
* event records and scores
That data is scoped to that one app and your account. Apps can't read each
other's data. Usage [analytics](/apps/operate/analytics-and-health) show aggregates and suppress small cohorts. Commerce reports are different: they can show shortened buyer wallet addresses and transaction links, which should not be described as anonymous.
***
## The biometric Score
The Score is a single, cross-app measure (0 to 100) built from **derived**
session results, such as how much calmer you got during a session compared
with your own baseline. Raw signal is never part of it.
* **Off by default.** Nothing is shared until you opt in.
* **Asked by Elata, not the app.** The consent prompt appears in Elata's own interface.
* **Enforced on the server.** Every result is checked against your current consent before it counts.
* **Revocable.** You can turn sharing off at any time.
* **Versioned.** If the consent terms change materially, you're asked again.
You can see your Score and activity on your **Insights** page, and inside apps
that ask for it. See [Score and insights](/users/score-and-insights).
***
## Your rights
Under Elata's privacy policy you can ask to access, correct, delete, export, or
restrict your personal data. Email [info@elata.bio](mailto:info@elata.bio); the
policy commits to responding within 30 days.
Each app on Elata is built by an independent developer. Read an app's own terms
if it links to any.
***
## What's next
| Capability | Status |
| - | - |
| On-device processing | Live |
| Opt-in Score with revocable consent | Live |
| Federated learning: improving models by training across devices without collecting raw data | Planned (research) |
See [Research directions](/overview/science/research-directions).
***
## Sources
* [Elata privacy policy](https://elata.bio/privacy)
# Data for Research
Source: https://docs.elata.bio/overview/science/data-for-research
What biosignal data exists in the Elata ecosystem, what doesn't, and the tools available for running your own studies.
## What data exists
Elata is built so that raw biosignals don't need to leave the user's device.
That shapes what data exists:
| Data | Exists? | Where |
| - | - | - |
| Raw camera frames | Not collected by Elata's server pipeline | Processed locally |
| Raw EEG and PPG | Not collected by Elata's server pipeline; local experimental recordings are possible | The user's browser storage when a compatible recording host is enabled |
| Derived session results for the biometric Score | Yes, with consent | Elata, for users who opt in |
| App saves, records, and scores | Yes | The user's account, scoped to each app |
| Aggregate app analytics | Yes | Visible to each app's builder, anonymized |
Elata does not currently offer research datasets or data access programs.
***
## Running your own study
Researchers can use the open-source tools directly:
* **Build a study as an Elata app.** Use the SDK for capture and processing, and publish it on Elata to reach participants in their browser. Your app is responsible for its own consent and study protocol. Apps may not send raw biosignals off the device under Elata's [submission policy](/apps/build/review-and-policy).
* **Use the SDK in your own tools.** The sensor packages work in any web app, outside Elata, under your own institution's data handling.
***
## Recording sessions (experimental)
Experimental. The SDK supplies the recording contract. The appstore's `feat/long-local-analytics` branch implements a local recording host, session pages, and storage controls, but explicitly disables them in production.
`biosignal-session` records multi-sensor sessions (EEG, PPG, rPPG metrics, and
more) as typed streams in Apache Arrow files, designed for reliability:
* every chunk carries a checksum and a stable identity, so it can be stored and retried safely
* real gaps are recorded as explicit discontinuities, never interpolated
* time is a single session clock in integer microseconds
Recording is local: the package has no network client. The application that
embeds it decides where data is stored. See
[biosignal-session](/sdk/biosignal-session/getting-started).
***
## Analysis tools (experimental)
`biosignal-analytics` provides reproducible features and transparent scores,
with every value traceable to the algorithm and version that produced it. See
[Metrics and scores](/overview/science/metrics-and-scores).
# EEG Headbands
Source: https://docs.elata.bio/overview/science/eeg
What consumer EEG headbands measure, how the Elata SDK processes it, and what calm and focus scores can and can't claim.
## The principle
Neurons communicate with tiny electrical currents. When large groups fire
together, the combined activity can be picked up by sensors on the scalp. That
recording is an **electroencephalogram (EEG)**. It's dominated by rhythms at
different frequencies:
| Band | Approx. range | Often associated with |
| - | - | - |
| Delta | 1–4 Hz | Deep sleep |
| Theta | 4–8 Hz | Drowsiness, some memory tasks |
| Alpha | 8–12 Hz | Relaxed wakefulness, especially with eyes closed |
| Beta | 13–30 Hz | Active thinking, alertness |
| Gamma | 30+ Hz | Fast processing; also heavily contaminated by muscle activity |
These associations are tendencies across groups of people, not rules for any
single moment.
***
## Consumer headbands
The Elata SDK supports the Muse family over Web Bluetooth:
| Device | EEG channels | Sensor positions |
| - | - | - |
| Muse 2, Muse S | 4 | Behind the ears (TP9, TP10) and on the forehead (AF7, AF8) |
| Muse S (Athena) | 8 | Additional channels on the newer protocol |
Research-grade systems use 32 to 256 wet electrodes in a cap. A headband uses a
handful of dry sensors in fixed positions. That makes it comfortable and
affordable, and it limits what it can measure.
***
## How the Elata SDK processes EEG
`eeg-web` runs in the browser using WebAssembly:
* **Filtering and spectra:** removes drift and line noise, and computes the frequency spectrum of each window
* **Band powers:** absolute, relative, and log power per band and channel
* **Quality flags:** flatline, clipping, extreme amplitude, and line noise, so bad windows can be excluded
* **Simple models:** for example, an alpha-rhythm detector and a calmness estimate built on band powers
***
## What affects the signal
| Factor | Effect |
| - | - |
| **Blinks and eye movement** | Large spikes on the forehead sensors |
| **Jaw clenching, talking, frowning** | Muscle activity that overwhelms higher frequencies |
| **Poor contact** | Hair, dry skin, or a loose fit cause noise or flat channels |
| **Movement** | Cable and sensor motion add slow drifts and bursts |
| **Few, fixed sensors** | Activity can't be located precisely, and some brain areas aren't covered |
Studies comparing consumer headbands with research systems find that they can
capture real brain signals. Event-related potentials have been recovered with
Muse, and four Muse electrodes performed close to a 32-electrode dataset on
one emotion-classification task. But signal quality varies by device and
setup: one 2024 comparison found Muse S had the weakest alignment with a
research-grade amplifier of the devices tested.
***
## What calm and focus scores can claim
A "calm" or "focus" score is a **product interpretation** built from band
powers and other inputs. It is not a direct readout of a mental state.
* Scores are most meaningful **relative to the same person's baseline**, not compared between people.
* They should come with **quality information** and be withheld when the signal is poor.
* Some popular measures don't hold up. For example, the SDK's Focus score deliberately does **not** use the theta/beta ratio, because that ratio isn't a valid measure of attention.
See [Metrics and scores](/overview/science/metrics-and-scores).
***
## Sources
Peer-reviewed literature, retrieved via PubMed:
* Krigolson OE, et al. Choosing MUSE: validation of a low-cost, portable EEG system for ERP research. *Frontiers in Neuroscience*, 2017. [doi:10.3389/fnins.2017.00109](https://doi.org/10.3389/fnins.2017.00109)
* Mikhaylov D, et al. Comparison of EEG signal spectral characteristics obtained with consumer- and research-grade devices. *Sensors*, 2024. [doi:10.3390/s24248108](https://doi.org/10.3390/s24248108)
* Garcia-Moreno FM, et al. EEG headbands vs caps: how many electrodes do I need to detect emotions? The case of the MUSE headband. *Computers in Biology and Medicine*, 2024. [doi:10.1016/j.compbiomed.2024.109463](https://doi.org/10.1016/j.compbiomed.2024.109463)
SDK implementation: [`eeg-web`](https://github.com/Elata-Biosciences/elata-bio-sdk/tree/main/packages/eeg-web) and [`biosignal-analytics`](https://github.com/Elata-Biosciences/elata-bio-sdk/tree/main/packages/biosignal-analytics) (public SDK repository).
# Metrics and Scores
Source: https://docs.elata.bio/overview/science/metrics-and-scores
How Elata turns signals into features and scores, why scores withhold themselves, and how the biometric Score is built.
There are two different things in this area, with different statuses:
| | What it is | Status |
| - | - | - |
| **SDK headline scores** | Open-source score formulas in `biosignal-analytics` that builders can run on their own data | Experimental (source only, not on npm) |
| **The biometric Score on Elata** | One cross-app Score per user, built from derived results apps report with consent | Live |
***
## From signals to features
Before any score, the SDK computes **features**: well-defined, reproducible
numbers.
* **EEG window features:** band powers (absolute, relative, log), spectral entropy, dominant frequency, alpha peak, Hjorth parameters, and quality flags
* **Pulse features:** heart rate, RMSSD, SDNN, and signal quality
* **Robust statistics:** medians and median absolute deviations instead of means, so one bad sample doesn't skew the result
* **Personal baselines:** a rolling 30-day median and range for each person, with outlier rejection
***
## SDK headline scores
Experimental. `biosignal-analytics` is available as source code and may
change before its first npm release.
| Score | Describes |
| - | - |
| **Measurement Quality** | The recording, not the person. Always available alongside any other score. |
| **Activation** | How far physiological arousal moved from the person's baseline |
| **Recovery** | How well arousal came back down after an activation |
| **Focus** | Engagement during a task |
| **Readiness** | Day-to-day state against the personal baseline |
| **Resilience** | How consistently someone recovers over time |
These are **product interpretations, never measurements**, and the SDK says
so. Two design rules keep them honest:
1. **Every contributor is visible.** Each score exposes its inputs with their weights, standardized values, and quality.
2. **Scores withhold themselves.** Instead of guessing, a score returns no value plus a reason when it can't be computed responsibly:
| Reason | Meaning |
| - | - |
| Inputs missing | The measurement wasn't made |
| Insufficient quality | It was made too poorly to read |
| Insufficient baseline | There's no usable personal baseline to compare with |
| No task context | Focus: nothing was being attended to |
| Insufficient history | Readiness needs 14 qualified days; Resilience needs 21 days plus 6 recovered activations |
A missing input never silently becomes a neutral middle value.
***
## The biometric Score on Elata
The Score is a single number from 0 to 100 that reflects a user's results
across all the apps they use, **only if they opt in**.
How it's built:
* **Apps report derived session results, never raw signal.** A report says, for example, that the user's calm level went from 0.42 at baseline to 0.61 during a five-minute session, with its signal quality and confidence.
* **Improvement relative to the user's own baseline matters**, not absolute values that differ between people.
* **Low-quality sessions are dropped**, and the Score shows as "calibrating" until there are enough good sessions to be stable.
* **Consent is checked on the server** for every report, so an app can't contribute without it.
* **Only approved apps can contribute.** An app needs a permission granted by Elata before its reports count.
See [Privacy and consent](/overview/privacy-and-consent) and, for builders,
[Consent and insights](/apps/platform/consent-and-insights).
***
## Sources
* [`biosignal-analytics` README](https://github.com/Elata-Biosciences/elata-bio-sdk/tree/main/packages/biosignal-analytics) (public SDK repository)
* [Metrics and scores](/apps/platform/metrics-and-scores) (builder docs)
# Science at Elata
Source: https://docs.elata.bio/overview/science/overview
Elata's scientific stance: what the measurements are for, how claims are graded, and where to read more.
## Our stance
Elata builds tools for **wellness, games, learning, and research**. The
signals Elata apps use (camera heart rate, consumer EEG, and headband PPG) are
real physiological measurements, but they come from consumer devices in
uncontrolled settings. They are estimates, not clinical readings.
Three rules follow from that:
1. **No diagnosis.** Elata and Elata apps don't diagnose, treat, or monitor medical conditions.
2. **Show the uncertainty.** Measures come with signal-quality and confidence values, and scores withhold themselves when the data isn't good enough rather than guessing.
3. **Grade every claim.** A measured heart rate and a "focus" score are different kinds of claim, and the SDK labels them differently.
***
## How claims are graded
The SDK's analytics package (`biosignal-analytics`, currently
[experimental](/overview/status)) gives every metric a registered definition
with two labels.
**Measurement class:** what kind of number it is.
| Class | Meaning | Example |
| - | - | - |
| Measured | Read more or less directly from the signal | Heart rate |
| Derived (deterministic) | Computed from measured values by a fixed formula | HRV (RMSSD), band power |
| Model-inferred | Estimated by a statistical or learned model | A learned waveform reconstruction |
| Product composite | A product interpretation combining several inputs | Focus, Readiness |
**Evidence tier:** how strongly the metric is supported.
| Tier | Meaning |
| - | - |
| Beta default | Well supported; shown by default |
| Advanced | Supported, but for informed users |
| Experimental | Early; may change or be removed |
| Rejected | Evaluated and deliberately not used |
Each value also records the exact algorithm and version that produced it, so
it can be traced and recomputed when an algorithm improves. Feature code is
tested against reference implementations (NumPy/SciPy) so the numbers agree
across the Rust, WebAssembly, and JavaScript versions.
***
## In this section
How a webcam estimates pulse, and its limits
What four dry sensors can and can't tell you
Optical heart rate and heart-rate variability
How derived measures and scores are built
What data exists, and what doesn't
Planned and archived research work
***
## Sources
* [`biosignal-analytics` README and registry](https://github.com/Elata-Biosciences/elata-bio-sdk/tree/main/packages/biosignal-analytics) (public SDK repository)
# PPG and Heart-Rate Variability
Source: https://docs.elata.bio/overview/science/ppg-and-hrv
Optical heart rate from a headband, heart-rate variability, and the conditions that make HRV meaningful.
## Contact PPG
**Photoplethysmography (PPG)** shines light into the skin and measures how much
comes back. As blood volume pulses with each heartbeat, the reflected light
changes. It's the same principle as rPPG, but with a sensor touching the skin,
which gives a much stronger signal. Smartwatches use it, and so do some
headbands, such as Muse S.
The Elata SDK's `ppg-web` package reads a headband's optical sensor over Web
Bluetooth and estimates heart rate and heart-rate variability on the device.
***
## Heart-rate variability (HRV)
Your heart doesn't beat like a metronome. The time between beats varies
slightly from beat to beat, and that variation is **heart-rate variability**.
Higher HRV at rest is often linked with recovery and relaxation, and lower HRV
with stress or fatigue, though it also varies with age, fitness, breathing,
and time of day.
The SDK reports two common time-domain measures:
| Measure | What it is |
| - | - |
| **RMSSD** | Root mean square of successive differences between beats. Reflects short-term, beat-to-beat variation. |
| **SDNN** | Standard deviation of the beat-to-beat intervals over the recording. Reflects overall variation. |
Strictly, variability measured from PPG is **pulse-rate variability**. It
closely tracks ECG-based HRV at rest, but the two aren't always
interchangeable.
***
## What makes HRV meaningful
| Condition | Why |
| - | - |
| **At rest** | Agreement between wearable and ECG-based HRV is very good to excellent at rest, and declines as activity increases |
| **Long enough** | A minute or more of clean data; a few seconds isn't enough |
| **Clean beat detection** | One missed or extra beat distorts RMSSD badly, so noisy segments must be excluded |
| **Compared to yourself** | HRV differs widely between people; trends against your own baseline are more informative than absolute numbers |
The SDK withholds HRV values (returns no value) when it can't detect beats
reliably, instead of reporting a misleading number.
***
## What it can't claim
PPG-based heart rate is widely used and generally accurate at rest. It does
not diagnose arrhythmias or other heart conditions: published reviews note that
ECG confirmation remains necessary for diagnosis.
***
## Sources
Peer-reviewed literature, retrieved via PubMed:
* Georgiou K, et al. Can wearable devices accurately measure heart rate variability? A systematic review. *Folia Medica*, 2018. [doi:10.2478/folmed-2018-0012](https://doi.org/10.2478/folmed-2018-0012)
* Hejjel L, Béres S. Comment on "Pulse rate variability in cardiovascular health: a review on its applications and relationship with heart rate variability". *Physiological Measurement*, 2021. [doi:10.1088/1361-6579/abd332](https://doi.org/10.1088/1361-6579/abd332)
* De Wever M, et al. Photoplethysmography for heart rate and rhythm monitoring: current evidence and clinical applications. *JACC: Clinical Electrophysiology*, 2026. [doi:10.1016/j.jacep.2026.06.034](https://doi.org/10.1016/j.jacep.2026.06.034)
SDK implementation: [`ppg-web`](https://github.com/Elata-Biosciences/elata-bio-sdk/tree/main/packages/ppg-web) (public SDK repository).
# Research Directions
Source: https://docs.elata.bio/overview/science/research-directions
Planned research directions and earlier research projects, each clearly labelled.
Nothing on this page is part of the live product. Each item is labelled so
it's clear what is planned and what is history. For what you can use today,
see [What's live](/overview/status).
***
## Federated learning
**Status: Planned (research).**
Federated learning trains a shared model across many devices without
collecting their raw data in one place. Each device improves the model locally
and only model updates are combined. It fits Elata's on-device design, because
it could let signal-processing models improve without raw biosignals ever
leaving users' devices.
It is a research direction, not a live feature. There is no federated
training in Elata or the Elata SDK today.
***
## Open EEG hardware: ElataEEG
**Status: Archived.**
ElataEEG is an open-source EEG hardware project. In 2025 it was upgraded from
8 to 32 channels with reduced noise. It's no longer under active development,
and the current Elata SDK targets consumer headbands instead. See
[Elata EEG](/home/elata-eeg/elata-eeg-overview) in the Archive and the public
[elata-eeg repository](https://github.com/Elata-Biosciences/elata-eeg).
***
## On-chain research: ZORP
**Status: Archived.**
ZORP is an on-chain research platform for encrypted data contribution and
automated participant compensation, which reached testnet in 2025. It's no
longer under active development. See [ZORP](/zorp-protocol/zorp-intro) in the
Archive and the public [zorp repository](https://github.com/Elata-Biosciences/zorp).
***
## Sources
* [Elata 2025 year in review](https://elata.bio/blog/2025-year-in-review-elata)
* [elata-eeg](https://github.com/Elata-Biosciences/elata-eeg) and [zorp](https://github.com/Elata-Biosciences/zorp) (public repositories)
# Camera Heart Rate (rPPG)
Source: https://docs.elata.bio/overview/science/rppg
How a webcam estimates pulse, how the Elata SDK does it, and the conditions that limit accuracy.
## The principle
With every heartbeat, the volume of blood in the skin rises and falls. Blood
absorbs light, so the skin's color changes very slightly in time with the
pulse. A camera can detect this change even though it's invisible to the eye.
Measuring it without contact is called **remote photoplethysmography (rPPG)**.
***
## How the Elata SDK does it
`rppg-web` runs this pipeline in the browser, on the user's device:
1. **Find the face.** A face-landmark model locates skin regions such as the forehead and cheeks.
2. **Average the color.** For each video frame, the SDK averages the red, green, and blue values in those regions.
3. **Extract the pulse signal.** Established methods combine the color channels to cancel out lighting and motion: the green-channel method, CHROM (chrominance-based), and POS (plane-orthogonal-to-skin).
4. **Estimate the rate.** The SDK estimates beats per minute two ways, from the signal's frequency spectrum and from its autocorrelation, and checks that they agree.
5. **Report quality.** Every estimate comes with a signal-quality value and a confidence value, so an app can tell a clean reading from a guess.
Raw video frames never leave the device.
***
## What affects accuracy
rPPG works well under good conditions and degrades under poor ones. Published
reviews consistently identify the same challenges:
| Factor | Why it matters |
| - | - |
| **Motion** | Head movement changes how light hits the skin, which can swamp the pulse signal |
| **Lighting** | Dim, uneven, or flickering light (some LED and fluorescent lights) adds noise |
| **Skin tone** | Darker skin absorbs more light, which lowers signal amplitude for optical methods |
| **Camera and compression** | Low frame rates and heavy video compression remove the tiny color changes rPPG depends on |
| **Distance and framing** | Fewer skin pixels mean a weaker signal |
Tips for users are on [Devices](/users/devices). Builders can use the SDK's
quality and confidence values to prompt users or hold back a reading.
***
## What rPPG can and can't claim
* **Can:** estimate pulse rate at rest in reasonable conditions, and show how steady the signal is.
* **With caution:** heart-rate variability from a camera needs very clean, longer recordings.
* **Can't:** diagnose heart rhythm problems or replace a medical device. Even contact PPG, used in smartwatches, needs ECG confirmation for diagnosis.
***
## Sources
Peer-reviewed literature, retrieved via PubMed:
* Debnath U, Kim S. A comprehensive review of heart rate measurement using remote photoplethysmography and deep learning. *Biomedical Engineering Online*, 2025. [doi:10.1186/s12938-025-01405-5](https://doi.org/10.1186/s12938-025-01405-5)
* Chen W, et al. Deep learning and remote photoplethysmography powered advancements in contactless physiological measurement. *Frontiers in Bioengineering and Biotechnology*, 2024. [doi:10.3389/fbioe.2024.1420100](https://doi.org/10.3389/fbioe.2024.1420100)
* De Wever M, et al. Photoplethysmography for heart rate and rhythm monitoring: current evidence and clinical applications. *JACC: Clinical Electrophysiology*, 2026. [doi:10.1016/j.jacep.2026.06.034](https://doi.org/10.1016/j.jacep.2026.06.034)
SDK implementation: [`rppg-web`](https://github.com/Elata-Biosciences/elata-bio-sdk/tree/main/packages/rppg-web) (public SDK repository).
# What's Live
Source: https://docs.elata.bio/overview/status
The status of every Elata capability: live, experimental, planned, or retired.
This page distinguishes current capabilities, experimental integrations, and future work. Other pages link here for availability.
Source review: October 11, 2026. Billing and Commerce guidance reflects the `fix/iap-system-overhaul` branch, not proof that every flow is deployed at app.elata.bio. Confirm the deployed UI and payment configuration before accepting payments.
| Label | Meaning |
| - | - |
| **Live** | Available now to everyone. |
| **Experimental** | Usable, but may change or have limits. |
| **Planned** | Direction we're working toward. Not available yet. |
| **Retired** | Removed. Listed so old links and posts make sense. |
***
## Elata (app.elata.bio)
| Capability | Status | Notes |
| - | - | - |
| Browse, play, save, review, and follow apps | Live | [Explore apps](/users/explore-apps) |
| Publish web apps (Build an App wizard, review, sandboxed hosting) | Live | [Quickstart](/apps/quickstart) |
| Sign in with social login or an existing wallet | Live | |
| Elata Points, quests, referrals, leaderboard | Live | [Points](/overview/points) |
| Biometric Score, consent, and Insights | Live | Opt-in. [Privacy and consent](/overview/privacy-and-consent) |
| Reminders from apps (notifications) | Live | Delivered by Elata, never by the app directly |
| In-app purchases (USDC) | Implemented; deployment unverified | The overhaul checkout uses the USDC splitter on Base; availability requires payment configuration. Check the deployed checkout's network. [In-app purchases](/apps/platform/in-app-purchases) |
| App subscriptions | Experimental | Manual calendar-month payments through the Base splitter; configuration and deployment must be verified. [Billing and purchases](/users/billing-and-purchases) |
***
## Elata SDK
Repository APIs and published packages can differ. Check the installed package version and release notes before using source-only APIs; publication labels below are not a guarantee that every current-source feature is in the npm release.
| Capability | Status | Notes |
| - | - | - |
| `rppg-web`, `eeg-web`, `eeg-web-ble`, `ppg-web` | Live | On npm. [SDK overview](/sdk/overview) |
| `app-metrics`, `app-payments` | Live | On npm |
| `create-elata-demo` (starter apps) | Packaging blocker in repository version 0.12.1 | Missing `app-metrics` version metadata prevents the packaged CLI from starting. Verify a fixed release before using it. |
| `app-state` | Experimental | Not on npm yet. Apps can use the [`elata:state:*` messages](/apps/platform/app-state) directly. |
| `biosignal-session`, `biosignal-analytics`, `rppg-models-web` | Experimental | Source code only, not on npm yet. APIs may change. |
| Muse 2, Muse S, Muse S (Athena) over Web Bluetooth | Live | Chrome or Edge. [Devices](/users/devices) |
| Any webcam for rPPG | Live | |
***
## Data and research
| Capability | Status | Notes |
| - | - | - |
| On-device processing of raw signals | Live | Raw signals do not need to leave the device |
| Local session storage, session detail, and on-device analytics | Experimental; disabled in production | Implemented on `feat/long-local-analytics` behind a developer flag. Recordings stay in that browser. [Local sessions and insights](/users/local-sessions-and-insights) |
| Federated learning (training models without collecting raw data) | Planned | Research direction. Not part of the live product. [Research directions](/overview/science/research-directions) |
***
## ELTA token
| Capability | Status | Notes |
| - | - | - |
| ELTA token on Base | Live | Deployed, with no official liquidity pool yet. [ELTA token](/overview/elta-token) |
Elata's wider on-chain protocol (staking, app tokens, governance) is not part of
the live product. Its source is in the public
[elata-protocol](https://github.com/Elata-Biosciences/elata-protocol)
repository, which notes it is pending an external audit.
***
## Retired from the current product
App tokens, staking, and governance are retired for now, with a planned return later. No return date or final design is committed. Earlier documentation is historical; it does not describe today's publishing or payment flow. Other archived projects are listed separately.
| Capability | What replaced it |
| - | - |
| Launching a token for each app, bonding curves, and trading app tokens | Apps are registered on Elata with a one-time fee; no token is launched |
| Staking for apps, tournaments, and veELTA pages in these docs | Not part of the live product |
| "Premium content" | [In-app purchases](/apps/platform/in-app-purchases) and [subscriptions](/apps/platform/subscriptions) |
| The API Reference tab | [Host bridge](/apps/platform/host-bridge) for builders |
| ElataEEG hardware, ZORP, AgentForge, NewsReader | Archived projects. See the [Archive](/archive/overview). |
# Welcome to Elata
Source: https://docs.elata.bio/overview/welcome
What Elata is, who it's for, and how to find your way around these docs.
## What Elata is
Elata is a platform for building and using consumer-friendly biosensing, neuroscience, and cognitive applications that respect your privacy.
* **Elata** ([app.elata.bio](https://app.elata.bio)) is where people find and play these apps in the browser, and where builders publish them.
* **The [Elata SDK](/overview/elata-sdk)** is a set of open-source packages that turn a webcam or a consumer headband into a biosignal sensor, with processing that runs on the user's own device.
* **Privacy starts on your device.** Raw signal processing runs locally. App saves, event records, scores, and payment records can be stored by Elata; derived results contribute to the cross-app biometric Score only with consent. You can revoke that consent and request deletion. [Your data](/users/your-data) explains the different storage and deletion paths.
You *don't need special hardware* to begin: camera-based apps can use the webcam or phone camera you already have.
Apps on Elata can turn an ordinary webcam or phone camera into a biosensor, using subtle changes in your face and behavior to measure or estimate signals like heart rate, heart rate variability, breathing rate, blink rate, affective state, and more.
Available outputs and reliability depend on the app, sensor, package version, signal quality, and algorithm. Heart rate, HRV, respiration estimates, and face-derived affect are different signals; a physiological activation score is not a direct measurement of emotion. See [Biosignals explained](/overview/biosignals) and [SDK output guidance](/sdk/rppg-web/getting-started#output-and-quality-guidance).
We also support applications that can read brain activity over the web. These applications rely on consumer-grade EEG devices, such as [Muse](https://choosemuse.com/).
Elata apps are for wellness, games, learning, and research. They are not
medical devices and don't diagnose or treat any condition. See
[Safety and limitations](/users/safety-and-limitations).
***
## Building technology that’s open, transparent, and private
Elata is built around open-source software, transparent experimentation, and the idea that biosensing technology should be understandable, inspectable, extensible, and privacy-focused to the people using it.
Much of the technology behind Elata is developed publicly on GitHub, from the SDKs that power today’s apps to earlier experiments that helped shape what Elata has become.
A couple notable examples:
* [**Elata EEG**](https://github.com/Elata-Biosciences/elata-eeg) is our open-source effort to build a low-cost, right-to-repair EEG hardware device for neuroscience research and fun DIY Brain-Computer Interface (BCI) projects, such as the [brain-controlled gaming competition](https://x.com/DeSciWorld/status/1978762697022865576?s=20) we did in Singapore in 2025.
* [**ZORP**](https://github.com/Elata-Biosciences/zorp) is an experiment we did to initiate permissionless research infrastructure for collecting data, coordinating scientific studies, and rewarding participants.
Not every experiment becomes part of the current Elata product, but we keep this work public because useful ideas, code, and research shouldn’t disappear when a project changes direction.
You can explore our current and past work on [GitHub](https://github.com/Elata-Biosciences).
***
## Who these docs are for
Find apps, play them, and control your data
Build a biosignal app and publish it on Elata
How the measurements work and where they fall short
Work together with the Elata community or team
Bring your hardware to the Elata SDK
Discord, X, Farcaster, and GitHub
***
## How to read these docs
Every capability in these docs has a status, so you always know what you can use
today:
| Label | Meaning |
| - | - |
| **Live** | Available now to everyone. |
| **Experimental** | Usable, but may change or have limits. For example, a package that is only available as source code. |
| **Planned** | Direction we're working toward. Not available, and details may change. |
| **Retired** | Existed before and has been removed. Kept here only so old links make sense. |
Pages that aren't fully live carry a tag in the sidebar. The full list is on
the [Status](/overview/status) page.
***
## Where to start
The ecosystem map: devices, SDK, apps, and Elata
Suggested reading for each audience
# Protocol Requirements
Source: https://docs.elata.bio/protocol-requirements
Collect the protocol, BLE, and metadata details needed before coding a device adapter.
Do this work before implementation starts.
A clean protocol inventory prevents most integration delays and avoids the
common failure mode where the adapter works only for one test session or one
firmware version.
## Required Handoff
Collect this information for every device and firmware line you plan to support.
| Area | What to capture |
| - | - |
| Discovery | device name patterns, service UUIDs, filters |
| GATT layout | characteristics, direction, notification/read/write behavior |
| Packet format | framing, byte order, headers, counters, checksums |
| EEG payload | channel names, channel count, sample rate, units, scaling |
| Timing | device timestamps, local timestamps, sequence behavior |
| Session control | any commands required to start or stop streaming |
| Failure modes | disconnect behavior, low battery behavior, firmware quirks |
## BLE Inventory Template
Use this as the minimum intake format:
```md theme={null}
## Device
- Model:
- Firmware:
- Transport: Web Bluetooth / bridge / native helper
## Discovery
- Device name prefix:
- Primary service UUIDs:
- Required optional services:
## Characteristics
- UUID:
- purpose:
- read / write / notify:
- notes:
## EEG payload
- channel names:
- channel count:
- sample rate:
- units and scaling:
- timestamp source:
## Session control
- how streaming starts:
- how streaming stops:
- reconnect notes:
```
## Packet Questions You Should Answer Early
| Question | Why it matters |
| - | - |
| How does a packet start and end? | The decoder needs a deterministic framing rule. |
| Is there a packet counter or sequence field? | This helps detect drops, reordering, and corruption. |
| Is there a checksum or CRC? | This helps reject bad packets safely. |
| How are samples packed? | The decoder needs the exact byte layout and endianness. |
| Can multiple time steps arrive in one packet? | This affects row batching and timestamp handling. |
| What changes across firmware versions? | You need predictable version gating and test coverage. |
## Metadata Rules
Your adapter should never guess the basics.
| Metadata | Rule |
| - | - |
| Channel names | Use a documented, stable order |
| Channel count | Match the emitted EEG rows exactly |
| Sample rate | Use the true output rate for the active mode |
| Units | Document whether values are raw, scaled, or converted |
| Clock source | Clearly state `device` or `local` |
Do not tune models or downstream app logic until this metadata is verified.
Most reliability issues start here.
## Browser and Platform Questions
This guide focuses on device integration, but platform constraints still matter.
Confirm these before planning a browser BLE rollout:
| Question | What to confirm |
| - | - |
| Does the device work over Web Bluetooth? | Chromium browsers only for this workflow |
| Does it require a secure context? | `https://` or `localhost` |
| Does it need a native helper? | If yes, use a separate package or bridge path |
| Does Safari or iOS matter? | Browser Web Bluetooth is not available there |
See [Compatibility](/sdk/operations/compatibility) for the current platform
expectations.
## Definition of Ready
You are ready to implement when you can answer all of the following without
guessing:
1. which transport path you are taking
2. how the browser discovers the device
3. how the session starts and stops
4. how packet bytes become EEG rows
5. how timestamps and channel metadata should be emitted
6. what caveats apply by firmware, browser, or platform
## Related Docs
* [Integration Paths](./integration-paths)
* [Transport Contract](./transport-contract)
* [Compatibility](/sdk/operations/compatibility)
* [Troubleshooting](/sdk/operations/troubleshooting)
## Next
Use the protocol inventory to implement a clean device layer.
Turn protocol assumptions into repeatable tests.
# FAQ
Source: https://docs.elata.bio/resources/faq
Answers to common questions about Elata, its apps, the Elata SDK, privacy, points, and the ELTA token.
## About Elata
A marketplace for privacy-respecting biosignal apps at [app.elata.bio](https://app.elata.bio), plus the open-source Elata SDK for building them. See [Welcome](/overview/welcome).
No. Elata apps are for wellness, games, learning, and research. They don't diagnose, treat, or monitor any medical condition. See [Safety and limitations](/users/safety-and-limitations).
The [What's live](/overview/status) page lists every capability as live, experimental, planned, or retired.
***
## Using apps
No. Camera apps work with any webcam. EEG and headband heart-rate apps need a supported headband such as Muse. See [Devices](/users/devices).
Camera apps work in modern browsers. Bluetooth headbands need Chrome or Edge (Web Bluetooth); Safari and iOS browsers don't support it. See [Devices](/users/devices).
No. You can sign in with a social login. Connecting an existing wallet is optional.
Browsing and playing are free. Some apps sell optional items or offer a monthly subscription. See [Monetization](/overview/monetization).
***
## Privacy
Raw signals are processed on your device, and apps aren't allowed to send them anywhere. See [Privacy and consent](/overview/privacy-and-consent).
An optional, cross-app measure built from derived session results. It's only built if you opt in, and you can turn it off at any time. See [Score and insights](/users/score-and-insights).
Email [info@elata.bio](mailto:info@elata.bio). See [Your data](/users/your-data).
***
## Building apps
Build a web app with the Elata SDK, then use the Build an App wizard at [app.elata.bio/build-app](https://app.elata.bio/build-app). See the [Quickstart](/apps/quickstart).
Yes. Uploads must use at least one Elata sensor package (rPPG, EEG, or PPG). See [Requirements](/apps/build/requirements).
A one-time registration fee in USDC, shown in the wizard.
Sell one-time items or charge a monthly subscription. See [Monetize your app](/apps/build/monetize).
Every release is scanned automatically and then reviewed by a moderator. You're notified on Elata and by email when it's approved.
***
## Points and the token
Off-chain points you earn for taking part: playing, reviewing, building, and inviting friends. They can't be bought, sold, or transferred. See [Elata Points](/overview/points).
No. See [ELTA token](/overview/elta-token).
No official liquidity pool exists yet. Any pool that appears before an official announcement is unauthorized. See [ELTA token](/overview/elta-token).
# Media & Branding Assets
Source: https://docs.elata.bio/resources/media-assets
Elata logos, logotypes, and brand assets.
[***A more extensive media asset collection/branding guide can be found on our Github and viewed here.***](https://github.com/Elata-Biosciences/branding-assets)
## Logotypes
### Elata Green (O.G.) Logotype
### Elata White (Dark Mode) Logotype
## Logos
### Elata Green (O.G.) Logo
### Elata White (Dark Mode) Logotype
### Elata Black Logo
# Getting Started
Source: https://docs.elata.bio/sdk/biosignal-analytics/getting-started
EEG features, HRV, robust statistics, and transparent headline scores with @elata-biosciences/biosignal-analytics.
`@elata-biosciences/biosignal-analytics` is not published to npm yet. The API below reflects the source in the [SDK repository](https://github.com/Elata-Biosciences/elata-bio-sdk/tree/main/packages/biosignal-analytics) and may change before the first release.
## What it does
`@elata-biosciences/biosignal-analytics` turns biosignal data into features and
scores in the browser, with no server or network connection:
* **EEG window features** (WASM): band powers, spectral entropy, dominant frequency, alpha peak, Hjorth parameters, and quality flags
* **HRV and robust statistics**: time-domain HRV, medians and MADs, rolling personal baselines
* **Headline scores**: Measurement Quality, Activation, Recovery, Focus, Readiness, and Resilience, each with its contributors exposed
* **A metric registry**: every metric has a registered definition, unit, evidence tier, and algorithm version
It pairs naturally with [`biosignal-session`](/sdk/biosignal-session/getting-started),
but doesn't require it.
```bash theme={null}
# Not on npm yet. This command works after the first release.
npm install @elata-biosciences/biosignal-analytics
```
***
## Analyze an EEG window
```ts theme={null}
import { analyzeEeg, initAnalyticsWasm } from '@elata-biosciences/biosignal-analytics';
await initAnalyticsWasm();
const result = await analyzeEeg({
samples, // samples[sampleIndex][channelIndex]
sampleRateHz: 256,
channels: ['TP9', 'AF7', 'AF8', 'TP10'],
});
```
One call per window keeps the JavaScript/WASM boundary cheap. The result
includes the configuration and algorithm versions that produced it.
***
## Headline scores
Scores are interpretations, not measurements, and they say so:
* **Measurement Quality** describes the recording, not the person, and is always available.
* **Activation, Recovery, Focus, Readiness, and Resilience** each list every contributor with its weight, z-score, and quality.
* A score **withholds itself** (returns `null` with a reason) rather than guessing when inputs are missing, quality is poor, or there's no personal baseline yet.
| Withhold reason | Meaning |
| - | - |
| `inputs_missing` | The measurement wasn't made |
| `insufficient_quality` | It was made too poorly to read |
| `insufficient_baseline` | No usable personal baseline yet |
| `no_activation_detected` | Recovery: nothing qualified as an activation |
| `recovery_incomplete` | Recovery: the recording ended before recovery |
| `no_task_context` | Focus: there was no task to attend to |
| `insufficient_history` | Readiness and Resilience: not enough days of data yet |
`insufficient_history` includes counts, so you can tell the user exactly how
many more days are needed. Readiness needs 14 qualified days; Resilience needs
21 plus 6 recovered activation episodes.
Focus is deliberately not based on the theta/beta ratio, which isn't a valid
attention measure.
***
## Run analysis off the main thread
```ts theme={null}
import {
createAnalyticsWorkerClient,
launchAnalyticsWorker,
} from '@elata-biosciences/biosignal-analytics';
const client = createAnalyticsWorkerClient({ createWorker: launchAnalyticsWorker });
```
***
## Entry points
| Import | Contents |
| - | - |
| `@elata-biosciences/biosignal-analytics` | Everything, re-exported |
| `…/registry` | Metric definitions, evidence tiers, algorithm versions |
| `…/insights` | Personal baselines and headline-score formulas |
| `…/worker` | The analytics worker entry |
| `…/testing` | Fixture loaders and synthetic signal helpers |
Feature code is verified against Python (NumPy/SciPy) reference fixtures, so the
Rust, WASM, and TypeScript implementations agree.
# Getting Started
Source: https://docs.elata.bio/sdk/biosignal-session/getting-started
Record multi-sensor biosignal sessions locally with @elata-biosciences/biosignal-session.
`@elata-biosciences/biosignal-session` is not published to npm yet. The API below reflects the source in the [SDK repository](https://github.com/Elata-Biosciences/elata-bio-sdk/tree/main/packages/biosignal-session) and may change before the first release.
## What it does
`@elata-biosciences/biosignal-session` records a biosignal session (EEG, PPG,
rPPG metrics, and more) as a set of typed streams, chunked into self-contained
[Apache Arrow](https://arrow.apache.org/) files. Recording is **local-only**:
the package has no network client.
It is built for reliability:
* every chunk carries a CRC32C checksum and an idempotent `(session, stream, sequence)` identity
* the recorder retries, applies backpressure, and never drops data silently
* real gaps are recorded as explicit discontinuities, never interpolated
***
## The model
```
Session ─┬─ Source (headset, camera, synthetic)
├─ Stream (eeg, ppg, optics, imu, rppg-metrics, ppg-metrics, …)
│ └─ Chunk (Arrow IPC file + CRC32C, sequence 0, 1, 2, …)
└─ Event (markers and annotations)
```
Time is session-relative integer **microseconds**, anchored once when the
session starts.
***
## Roles
A recording has two sides that talk over a `MessagePort`:
| Side | Responsibility | Provided by this package |
| - | - | - |
| **Recorder** (your app) | Buffers samples, closes chunks, encodes Arrow, checksums, retries | Yes: `RecorderCore`, device adapters, a worker launcher |
| **Host** (trusted embedder) | Stores chunks durably and acknowledges each one | A reference in-memory host for tests (`createMemoryHost`) |
The appstore has an experimental local host on `feat/long-local-analytics`, disabled in production. Use your own host page or the in-memory host for development and tests, or follow [Local sessions and insights](/users/local-sessions-and-insights) for the appstore development flow. This is browser-local storage, not cloud hosting.
***
## Install
```bash theme={null}
# Not on npm yet. This command works after the first release.
npm install @elata-biosciences/biosignal-session
```
| Import | Contents |
| - | - |
| `@elata-biosciences/biosignal-session` | Contracts, protocol types, error codes, time helpers, CRC32C |
| `@elata-biosciences/biosignal-session/browser` | `RecorderCore`, handshake, Arrow encode/decode, device adapters, worker launcher |
| `@elata-biosciences/biosignal-session/testing` | Synthetic source, in-memory host, fake clock, recorder harness |
***
## Device adapters
Wrap the SDK objects you already use as recording sources:
* `createHeadbandSource(transport)`: a `HeadbandTransport` from `eeg-web` / `eeg-web-ble`
* `createRppgSource(options)`: an `rppg-web` session
* `createPpgSource(options)`: `ppg-web` metrics
***
## Try it without hardware
The testing entry runs a full recording against an in-memory host with a
seeded synthetic source. Hours of virtual signal run in seconds:
```ts theme={null}
import {
createRecorderHarness,
createSyntheticSource,
} from '@elata-biosciences/biosignal-session/testing';
const h = createRecorderHarness();
const source = createSyntheticSource({ seed: 1234 });
await h.start();
await h.startSource(source);
source.pump(60_000); // 60 s of synthetic EEG, rPPG, and PPG
await h.advance(60_000);
await source.stop();
await h.finalize();
h.core.state(); // "complete"
```
***
## Read chunks back
Every chunk is a complete Arrow IPC file that carries its own schema and
identity, so you can decode one on its own, in JavaScript or in Python with
`pyarrow`:
```ts theme={null}
import { decodeChunk, readFloat32Column } from '@elata-biosciences/biosignal-session/browser';
const { table, identity } = decodeChunk(bytes);
const tp9 = readFloat32Column(table, 'TP9');
```
***
## Full guide
The step-by-step recording flow (clock anchor, handshake, opening streams,
pushing samples, finalizing) and the host's guarantees are in the
[recording guide](https://github.com/Elata-Biosciences/elata-bio-sdk/blob/main/docs/guides/using-biosignal-sessions.md).
AI tools can read `node_modules/@elata-biosciences/biosignal-session/llms.txt`.
To analyze what you record, see [biosignal-analytics](/sdk/biosignal-analytics/getting-started).
# Create Elata Demo
Source: https://docs.elata.bio/sdk/create-elata-demo
Scaffold a ready-to-run Elata starter app from a template.
**Known issue:** the repository's `create-elata-demo` 0.12.1 has a packaging blocker and fails to start without sibling workspace metadata. Verify a fixed release before using it. For now, follow
[Add rPPG to an existing app](/sdk/tutorials/rppg-existing-app) to set up
the SDK in your own web app instead. See [What's live](/overview/status).
## What it is
`@elata-biosciences/create-elata-demo` generates a working Vite + React app
wired to the Elata SDK. It's the fastest way to a running app, and a
known-good baseline before you integrate the SDKs into your own project.
***
## Templates
| Template | Aliases | What you get | Hardware |
| - | - | - | - |
| `rppg-demo` | `rppg` | Camera pulse and rPPG starter. **The default.** | Webcam |
| `eeg-demo` | `eeg` | Browser EEG starter for SDK wiring, with Muse Web Bluetooth support | Muse (optional) |
| `eeg-ble` | `ble` | Bluetooth-first EEG starter with the browser pairing flow | Muse |
| `ppg-demo` | `ppg`, `muse-ppg` | Headband PPG starter showing BPM, RMSSD, and live diagnostics | Muse |
| `pulse-game` | `pulse`, `recovery` | rPPG recovery game that runs in a sandboxed iframe and saves scores with `app-metrics`, end to end | Webcam |
Building for Elata? `pulse-game` shows the full Elata pattern:
the app runs in an iframe and talks to a host over the
[metrics bridge](/apps/platform/metrics-and-scores).
***
## Usage
```bash npm theme={null}
# Interactive: pick a template, then a project name
npm create @elata-biosciences/elata-demo my-app
# Pick a template directly
npm create @elata-biosciences/elata-demo my-app -- --template rppg
npm create @elata-biosciences/elata-demo my-app -- --template eeg-ble
npm create @elata-biosciences/elata-demo my-app -- --template ppg
npm create @elata-biosciences/elata-demo my-app -- --template pulse-game
# List templates
npx @elata-biosciences/create-elata-demo --list-templates
```
```bash pnpm theme={null}
pnpm create @elata-biosciences/elata-demo my-app
pnpm create @elata-biosciences/elata-demo my-app -- --template rppg
pnpm dlx @elata-biosciences/create-elata-demo --list-templates
```
| Flag | Meaning |
| - | - |
| `--template ` or `--template=` | Choose a template or alias |
| `--list-templates` | Print the available templates |
Without `--template`, the CLI asks you to pick one when run interactively, and
uses `rppg-demo` otherwise. If you leave out the project directory, it asks for
a name.
***
## What you get
Every generated app includes:
* a Vite + React + TypeScript app
* Elata packages pinned to a compatible set of versions
* a template-specific `README.md`
* `npm run dev`: start the dev server
* `npm run build`: type-check and build to `dist/`
* `npm run build:zip`: build and write `app.zip`, ready to [upload to Elata](/apps/build/launch-your-app)
```bash theme={null}
cd my-app
npm install
npm run dev
```
***
## Inside another pnpm workspace
If you scaffold inside an existing `pnpm` workspace that doesn't include the new
folder, install and run it outside the workspace:
```bash theme={null}
pnpm --dir my-app --ignore-workspace install
pnpm --dir my-app --ignore-workspace run dev
```
***
## Scaffolder or example apps?
| If you want... | Use |
| - | - |
| A clean starter with pinned dependencies | `create-elata-demo` |
| Full, product-shaped apps with routing, game loops, and charts | [Example apps](/sdk/guides/example-apps) |
***
## Next
Scaffold and run a starter app
Package decision guide
Upload your app
# Getting Started
Source: https://docs.elata.bio/sdk/eeg-web-ble/getting-started
Connect to EEG headband devices over Web Bluetooth
## When To Use This Package
Use `@elata-biosciences/eeg-web-ble` when your app needs:
* browser-side discovery of Muse-compatible devices
* live EEG streaming over Web Bluetooth
* normalized headband frames for app logic or downstream analysis
Install it alongside `@elata-biosciences/eeg-web`.
***
## Installation
```bash theme={null}
npm install @elata-biosciences/eeg-web-ble @elata-biosciences/eeg-web
```
`eeg-web-ble` depends on `eeg-web` for shared frame types and the WASM module.
***
## Requirements
* Browser with **Web Bluetooth** support (Chrome/Edge on desktop or Android)
* Served from a **secure context** (`https://` or `localhost`)
* Safari/iOS does **not** support Web Bluetooth. See [Muse device](/sdk/eeg-web-ble/muse-device) for alternatives
Check support before showing a "Connect" button:
```typescript theme={null}
import { checkWebBluetooth } from "@elata-biosciences/eeg-web-ble";
const support = checkWebBluetooth();
if (!support.supported) {
showMessage(support.message); // explains why, including iOS-specific guidance
}
```
***
## Basic Usage
`startStreaming()` is the recommended default. It wraps `connect()` and `start()` together and avoids the common mistake of connecting successfully but never beginning to stream.
```typescript theme={null}
import { initEegWasm, AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
import { BleTransport } from "@elata-biosciences/eeg-web-ble";
// Initialize WASM first
await initEegWasm();
// Create transport
const transport = new BleTransport({
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
},
});
// Handle incoming EEG frames
transport.onFrame = (frame) => {
console.log(`EEG samples: ${frame.eeg.samples.length} rows`);
console.log(`Channels: ${frame.eeg.channelNames.join(", ")}`);
};
// Handle connection status changes
transport.onStatus = (status) => {
console.log(`Transport: ${status.state}`, status.reason || "");
};
// Connect and start streaming (triggers Bluetooth device picker)
await transport.startStreaming();
// ... later
await transport.stop();
```
***
## BleTransport Lifecycle
| Method | What it does |
| - | - |
| `startStreaming()` | Recommended: connects and starts the stream in one call |
| `connect()` | Opens Bluetooth device picker, pairs, and prepares session |
| `start()` | Begins EEG data stream; `onFrame` callbacks fire |
| `stop()` | Stops the data stream; connection stays open |
| `disconnect()` | Releases the Bluetooth session |
***
## BleTransportOptions
```typescript theme={null}
const transport = new BleTransport({
sourceName: "my-app-ble", // name tag in frame.source
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(), // for Athena headbands
logger: (msg) => console.debug(msg),
onDisconnected: () => console.warn("Device disconnected"),
},
});
```
| Option | Type | Description |
| - | - | - |
| `sourceName` | `string` | Identifier included in `HeadbandFrameV1.source` |
| `deviceOptions` | `MuseDeviceOptions` | Passed to underlying `MuseBleDevice` |
| `device` | `BleDeviceLike` | Inject a custom device implementation |
***
## Athena Support
Muse S headbands with Athena firmware require an Athena decoder factory. Include it up front to keep both classic and Athena flows covered:
```typescript theme={null}
import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
import { BleTransport } from "@elata-biosciences/eeg-web-ble";
const transport = new BleTransport({
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
},
});
```
Athena headbands provide 8 EEG channels, optics, accelerometer/gyroscope, and battery data in each frame.
***
## Device Info
After connecting, you can query device metadata:
```typescript theme={null}
await transport.connect();
const isAthena = transport.getIsAthena();
const boardInfo = transport.getBoardInfo();
const channelNames = transport.getEegNames();
```
***
## Platform Caveats
* Safari and iOS do not provide usable Web Bluetooth support for this workflow.
* For Safari and iOS, use a native BLE shell, companion bridge, or hybrid WebView strategy with `@elata-biosciences/eeg-web` frame contracts as the boundary.
* Firmware variants may differ in command behavior.
***
## Next
Step-by-step BLE streaming guide
Protocol details, characteristics, and compatibility
End-to-end streaming and processing guide
# Muse Device
Source: https://docs.elata.bio/sdk/eeg-web-ble/muse-device
MuseBleDevice internals, protocol details, and compatibility
## MuseBleDevice
`MuseBleDevice` is the low-level Web Bluetooth class that handles device pairing, GATT characteristic subscriptions, and packet decoding. `BleTransport` wraps it to provide the normalized `HeadbandTransport` interface.
You typically do not use `MuseBleDevice` directly. Use `BleTransport` instead.
***
## Supported Protocols
| Protocol | Headbands | EEG Channels | Extra Data |
| - | - | - | - |
| **Classic** | Muse 2, Muse S (classic) | 4 (TP9, AF7, AF8, TP10) | PPG (3 channels) |
| **Athena** | Muse S (Athena firmware) | 8 (TP9, AF7, AF8, TP10, AUX1-4) | Optics, accgyro, battery |
Protocol is auto-detected during connection based on device characteristics.
***
## Classic Protocol
Classic Muse headbands expose 4 EEG channels at 256 Hz. Each channel has its own GATT characteristic:
| Characteristic | Channel |
| - | - |
| `273e0003-...` | TP9 |
| `273e0004-...` | AF7 |
| `273e0005-...` | AF8 |
| `273e0006-...` | TP10 |
PPG is available on 3 additional characteristics (`PPG1`, `PPG2`, `PPG3`).
***
## Athena Protocol
Athena headbands use a different packet format with two main characteristics:
| Characteristic | Data |
| - | - |
| `273e0013-...` | EEG (8 channels, multiplexed) |
| `273e0014-...` | Auxiliary (optics, accgyro, battery) |
Athena requires a decoder factory. Without one, connection will fail:
```typescript theme={null}
import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
const transport = new BleTransport({
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
},
});
```
***
## MuseDeviceOptions
```typescript theme={null}
interface MuseDeviceOptions {
athenaDecoderFactory?: AthenaDecoderFactory;
sleepMs?: (ms: number) => Promise;
logger?: (message: string) => void;
onDisconnected?: () => void;
}
```
***
## MuseBoardInfo
Returned by `getBoardInfo()` after connection:
```typescript theme={null}
interface MuseBoardInfo {
device_name: string;
sample_rate_hz: number; // 256
channel_count: number; // 4 or 8
eeg_channel_names: string[];
board_id: number;
protocol: "classic" | "athena";
optics_channel_count: number;
description: string;
}
```
***
## Browser Compatibility
| Browser | Platform | Status |
| - | - | - |
| Chrome 56+ | Windows, macOS, Linux, Android | Supported |
| Edge 79+ | Windows, macOS | Supported |
| Opera 43+ | Desktop | Supported |
| Safari | macOS, iOS | Not supported |
| Firefox | All | Not supported |
Web Bluetooth requires HTTPS. It will not work on `http://` (except `localhost` for development).
***
## Safari/iOS Workarounds
Three strategies for iOS support:
1. **Native app shell** (recommended): implement BLE in Swift with CoreBluetooth, bridge frames to web UI
2. **Companion bridge**: native app streams frames over WebSocket/WebRTC to the web app
3. **Hybrid WebView**: `WKWebView` with native message handlers for BLE
In all cases, use the `HeadbandFrameV1` schema as the interface boundary so browser and native transports emit the same frame shape.
***
## Next
Transport API and connection guide
Frame schema and transport interface
# Getting Started
Source: https://docs.elata.bio/sdk/eeg-web/getting-started
Install and initialize the @elata-biosciences/eeg-web WASM package
## When To Use This Package
Use `@elata-biosciences/eeg-web` when your app needs:
* EEG analysis APIs in the browser
* shared EEG web contracts used by higher-level integrations
* browser-side access to Elata WASM models and signal-processing helpers
This package does not implement Bluetooth device connection. Add `@elata-biosciences/eeg-web-ble` if you also need live Muse-compatible browser transport.
New to the SDK? Start by scaffolding a demo app with `create-elata-demo` before wiring packages manually. See the [First App tutorial](/sdk/tutorials/first-app).
***
## Installation
```bash pnpm theme={null}
pnpm add @elata-biosciences/eeg-web
```
```bash npm theme={null}
npm install @elata-biosciences/eeg-web
```
Requirements: Node.js 20+ for server-side usage and local repo tooling; modern browser with WebAssembly support for in-browser usage.
***
## WASM Initialization
Before using any signal processing or model APIs, initialize the WASM module:
```typescript theme={null}
import { initEegWasm } from "@elata-biosciences/eeg-web";
await initEegWasm();
```
`initEegWasm` is idempotent. Calling it multiple times returns the same promise. For synchronous initialization (e.g., in a Web Worker):
```typescript theme={null}
import { initEegWasmSync } from "@elata-biosciences/eeg-web";
initEegWasmSync(wasmModule);
```
***
## Basic Usage
Compute band powers from EEG data:
```typescript theme={null}
import { initEegWasm, band_powers } from "@elata-biosciences/eeg-web";
await initEegWasm();
const sampleRate = 256;
const eegSamples = new Float32Array(/* ... one channel of EEG samples ... */);
const powers = band_powers(eegSamples, sampleRate);
console.log("Alpha:", powers.alpha);
console.log("Beta:", powers.beta);
console.log("Theta:", powers.theta);
console.log("Delta:", powers.delta);
console.log("Gamma:", powers.gamma);
```
***
## Package Structure
`@elata-biosciences/eeg-web` is a thin TypeScript wrapper around WASM bindings generated by `wasm-bindgen`:
* `initEegWasm` / `initEegWasmSync`: WASM initialization helpers
* Headband frame types: normalized data schema for EEG transports
* All WASM APIs: re-exported from the generated bindings
### Key Exports
* `initEegWasm`
* `initEegWasmSync`
* `band_powers`
* `WasmAlphaBumpDetector`
* `WasmAlphaPeakModel`
* `WasmCalmnessModel`
* `AthenaWasmDecoder`
* `createRppgPipeline`
* Frame helpers: `getEegChannelSamples`, `getEegInterleavedSamples`, `getEegSignalBlock`
* Preprocessing: `createEegPreprocessor`, `processHeadbandFrame`
* Errors: `ElataError`, `isElataError`, `asElataError`
Generated `wasm-bindgen` exports are re-exported for compatibility and SDK debugging. Avoid instantiating generated wrappers directly unless you are intentionally debugging the SDK itself.
***
## Next
Not sure if this is the right package? See the decision guide.
Step-by-step integration tutorial
Band powers, FFT, spectrum analysis
Alpha bump detection, calmness scoring
Frame schema and transport interface
# Headband Transport
Source: https://docs.elata.bio/sdk/eeg-web/headband-transport
Normalized frame schema and transport interface for EEG headband devices
## HeadbandFrameV1
The canonical data frame emitted by all headband transports. Every implementation (BLE, native bridge, synthetic) produces this same shape:
```typescript theme={null}
interface HeadbandFrameV1 {
schemaVersion: "v1";
source: string; // e.g., "muse-ble"
sequenceId: number;
emittedAtMs: number;
eeg: HeadbandSignalBlock; // always present
ppgRaw?: HeadbandSignalBlock; // PPG (if available)
optics?: HeadbandSignalBlock; // Athena optics
accgyro?: HeadbandSignalBlock; // Athena accelerometer/gyroscope
battery?: HeadbandBatteryBlock; // battery level
}
```
***
## HeadbandSignalBlock
A block of time-series samples for one or more channels:
```typescript theme={null}
interface HeadbandSignalBlock {
sampleRateHz: number;
channelNames: string[]; // e.g., ["TP9", "AF7", "AF8", "TP10"]
channelCount: number;
samples: number[][]; // rows of [ch0, ch1, ch2, ch3]
timestampsMs?: number[];
clockSource?: "device" | "local";
}
```
***
## HeadbandTransportState
Transport lifecycle states. `HeadbandTransportState` is a string enum: `HeadbandTransportState.Streaming === "streaming"`.
| State | Description |
| - | - |
| `Idle` | Transport created, not connected |
| `Connecting` | Connection in progress |
| `Connected` | Connected, not streaming |
| `Streaming` | Actively receiving frames |
| `Degraded` | Connected but experiencing issues |
| `Reconnecting` | Attempting to reconnect |
| `Disconnected` | Disconnected from device |
| `Error` | Unrecoverable error |
***
## HeadbandTransport Interface
All transports implement this interface:
```typescript theme={null}
interface HeadbandTransport {
onFrame?: (frame: HeadbandFrameV1) => void;
onStatus?: (status: HeadbandTransportStatus) => void;
connect(): Promise;
disconnect(): Promise;
start(): Promise;
stop(): Promise;
}
```
**Lifecycle:**
```mermaid theme={null}
stateDiagram-v2
[*] --> Idle
Idle --> Connecting: connect()
Connecting --> Connected: success
Connected --> Streaming: start()
Streaming --> Connected: stop()
Connected --> Disconnected: disconnect()
Streaming --> Disconnected: disconnect()
Connecting --> Error: failure
Streaming --> Degraded: signal issues
Degraded --> Streaming: recovery
Disconnected --> Connecting: connect()
```
***
## HeadbandTransportStatus
Status updates emitted via `onStatus`:
```typescript theme={null}
interface HeadbandTransportStatus {
state: HeadbandTransportState;
atMs: number;
reason?: string;
errorCode?: string;
recoverable?: boolean;
details?: Record;
}
```
***
## Usage Pattern
```typescript theme={null}
const transport: HeadbandTransport = /* BleTransport or other */;
transport.onFrame = (frame) => {
const eegSamples = frame.eeg.samples;
// samples[sampleIdx][channelIdx]: each row is one time step [tp9, af7, af8, tp10]
};
transport.onStatus = (status) => {
console.log(`State: ${status.state}`, status.reason);
};
await transport.connect();
await transport.start();
// ... later
await transport.stop();
await transport.disconnect();
```
***
## Helpers
`@elata-biosciences/eeg-web` includes helpers for reshaping frames:
| Helper | Returns |
| - | - |
| `getEegChannelSamples(frame, channelIndex)` | One channel as a `Float32Array`, ready for `band_powers` |
| `getEegInterleavedSamples(frame)` | All channels interleaved `[s0_ch0, s0_ch1, …]`, for models such as `WasmCalmnessModel` |
| `getEegSignalBlock(frame, source?)` | The raw or processed EEG block |
***
## Next
Connect to headband devices
End-to-end streaming guide
# Analysis Models
Source: https://docs.elata.bio/sdk/eeg-web/models
Alpha bump detection, alpha peak tracking, and calmness scoring
All three models share the same shape:
* construct with `new Model(sampleRateHz, channelCount)`
* call `process(interleaved)` with a `Float32Array` of **interleaved** samples (`[s0_ch0, s0_ch1, …, s1_ch0, …]`); `getEegInterleavedSamples(frame)` produces this from a headband frame
* `process` returns a result object, or `undefined` until enough data has accumulated (`min_samples()` tells you how much)
* call `reset()` to clear state
Because `channelCount` is only known once the first frame arrives, construct models inside your first-frame handler.
## WasmAlphaBumpDetector
Detects alpha bumps: transient changes in alpha band power that indicate state transitions, such as eyes closing.
```typescript theme={null}
import { initEegWasm, WasmAlphaBumpDetector, getEegInterleavedSamples } from "@elata-biosciences/eeg-web";
await initEegWasm();
const detector = new WasmAlphaBumpDetector(256, 4); // sampleRateHz, channelCount
const result = detector.process(getEegInterleavedSamples(frame));
if (result?.state_changed) {
console.log(result.state, result.alpha_power, result.baseline); // state: "high" | "low" | "transitioning" | "unknown"
}
```
Tune with `set_threshold(multiplier)` and `set_baseline_smoothing(alpha)`.
***
## WasmAlphaPeakModel
Tracks the individual alpha peak frequency (typically 8 to 13 Hz), which varies from person to person.
```typescript theme={null}
import { WasmAlphaPeakModel } from "@elata-biosciences/eeg-web";
const model = new WasmAlphaPeakModel(256, 4);
const result = model.process(interleaved);
if (result) {
console.log(result.peak_frequency, result.smoothed_peak_frequency, result.snr);
}
```
***
## WasmCalmnessModel
Computes a calmness score from the ratio of alpha to beta power. Higher alpha relative to beta generally indicates a more relaxed state.
```typescript theme={null}
import { WasmCalmnessModel } from "@elata-biosciences/eeg-web";
const model = new WasmCalmnessModel(256, 4);
const result = model.process(interleaved);
if (result) {
console.log(result.score); // 0.0 (alert) to 1.0 (very calm)
console.log(result.smoothed_score); // less jittery
console.log(result.alpha_beta_ratio);
}
```
***
## AthenaWasmDecoder
Decodes raw Athena protocol packets from Muse S (Athena firmware) headbands. Used as a factory parameter for `BleTransport`:
```typescript theme={null}
import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
const decoder = new AthenaWasmDecoder();
decoder.reset();
decoder.set_use_device_timestamps(true);
decoder.set_clock_kind("monotonic");
decoder.set_reorder_window_ms(50);
const output = decoder.decode(rawBytes);
```
The Athena decoder is typically not used directly. It is passed as a factory to [`BleTransport`](/sdk/eeg-web-ble/getting-started):
```typescript theme={null}
import { BleTransport } from "@elata-biosciences/eeg-web-ble";
import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
const transport = new BleTransport({
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
},
});
```
***
## Next
Band powers, FFT, spectrum analysis
Frame schema and transport interface
# Signal Processing
Source: https://docs.elata.bio/sdk/eeg-web/signal-processing
Band power analysis, FFT, and spectrum computation
## Band Powers
Compute standard EEG frequency band powers from a sample array:
```typescript theme={null}
import { initEegWasm, band_powers } from "@elata-biosciences/eeg-web";
await initEegWasm();
const powers = band_powers(samples, sampleRateHz);
// powers.delta 0.5-4 Hz
// powers.theta 4-8 Hz
// powers.alpha 8-13 Hz
// powers.beta 13-30 Hz
// powers.gamma 30-100 Hz
```
`band_powers` returns a `WasmBandPowers` object with named fields for each standard band.
***
## Individual Band Functions
For targeted analysis, use individual band functions:
```typescript theme={null}
import {
alpha_power,
beta_power,
theta_power,
delta_power,
gamma_power,
custom_band_power,
} from "@elata-biosciences/eeg-web";
const alpha = alpha_power(samples, sampleRateHz);
const beta = beta_power(samples, sampleRateHz);
const theta = theta_power(samples, sampleRateHz);
const delta = delta_power(samples, sampleRateHz);
const gamma = gamma_power(samples, sampleRateHz);
// Custom frequency range
const mu = custom_band_power(samples, sampleRateHz, 8.0, 12.0);
```
***
## Power Spectrum
Compute the full power spectrum via FFT:
```typescript theme={null}
import { compute_power_spectrum, get_fft_frequencies } from "@elata-biosciences/eeg-web";
const spectrum = compute_power_spectrum(samples); // samples: Float32Array
const frequencies = get_fft_frequencies(samples.length, sampleRateHz);
// spectrum[i] is the power at frequencies[i] Hz
```
***
## Input Format
All signal processing functions expect:
| Parameter | Type | Description |
| - | - | - |
| `samples` | `Float32Array` | Single-channel EEG time series. Plain arrays are not accepted. |
| `sampleRateHz` | `number` | Sampling rate in Hz (e.g., 256 for Muse) |
For multi-channel data (e.g., from `HeadbandFrameV1.eeg.samples`), extract individual channels and process them separately:
```typescript theme={null}
const frame: HeadbandFrameV1 = /* ... from transport ... */;
const channelIndex = 0; // TP9
const channelSamples = frame.eeg.samples.map(row => row[channelIndex]);
const powers = band_powers(new Float32Array(channelSamples), frame.eeg.sampleRateHz);
```
***
## Next
Alpha bump detection, calmness scoring
Install and initialize eeg-web
# Choosing the right package
Source: https://docs.elata.bio/sdk/guides/choose-the-right-package
Pick the right Elata SDK package for what you're building.
## Start here
1. **New app or just evaluating?** Use [`create-elata-demo`](/sdk/create-elata-demo). You'll have a working app in minutes.
2. **Existing app?** Add the package for the capability you need, from the table below.
3. **Publishing to Elata?** Add the [Elata packages](#elata-packages) for purchases, storage, and scores.
***
## By goal
| I want to... | Use | Install with |
| - | - | - |
| Measure heart rate from a webcam | [`rppg-web`](/sdk/rppg-web/getting-started) | |
| Process EEG in the browser (band powers, models) | [`eeg-web`](/sdk/eeg-web/getting-started) | |
| Connect an EEG headband over Bluetooth | [`eeg-web-ble`](/sdk/eeg-web-ble/getting-started) | `eeg-web` |
| Get heart rate and HRV from a headband's PPG sensor | [`ppg-web`](/sdk/ppg-web/getting-started) | `eeg-web`, `eeg-web-ble`, `rppg-web` |
| Record a session from one or more sensors | [`biosignal-session`](/sdk/biosignal-session/getting-started) | the sensor packages you record from |
| Compute features, HRV, and headline scores | [`biosignal-analytics`](/sdk/biosignal-analytics/getting-started) | |
| Experiment with learned rPPG waveform reconstruction | `rppg-models-web` (experimental) | `rppg-web` |
***
## Elata packages
These only work when your app runs inside the [Elata](/apps/build/overview):
| I want to... | Use |
| - | - |
| Sell one-time items | [`app-payments`](/apps/platform/in-app-purchases) |
| Save settings and progress per user | [`app-state`](/apps/platform/app-state) |
| Record sessions and scores, or contribute to the biometric Score | [`app-metrics`](/apps/platform/metrics-and-scores) |
***
## Common mistakes
These cause most first-run problems.
* **Installing `eeg-web-ble` without `eeg-web`.** The Bluetooth transport depends on it.
* **Expecting Web Bluetooth in Safari or on iOS.** Use Chrome or Edge on desktop or Android. See [Compatibility](/sdk/operations/compatibility).
* **Serving over plain `http://`.** Camera and Bluetooth need `https://` or `localhost`.
* **Assuming a scaffolded app is broken inside another pnpm workspace.** Install it with `pnpm --dir my-app --ignore-workspace install`.
***
## Next
Scaffold your first app
Full reference implementations
Browser, device, and tooling support
# EEG + BLE integration
Source: https://docs.elata.bio/sdk/guides/eeg-ble-integration
Connect a Muse-compatible headband in the browser, stream EEG frames, and compute band powers with the Elata SDK.
Use this guide when you want the full browser EEG flow in one place. You will initialize the EEG WASM layer, connect over Web Bluetooth, stream frames, and turn those frames into app-ready metrics.
## What this guide covers
This guide shows how to combine `@elata-biosciences/eeg-web` and `@elata-biosciences/eeg-web-ble` into a single browser flow.
By the end, you will be able to:
* initialize the EEG WASM runtime
* connect to a Muse-compatible headband from a browser
* receive normalized `HeadbandFrameV1` frames
* compute real-time band powers from incoming EEG samples
* stop cleanly and recover from disconnects
## Before you start
You need:
* `@elata-biosciences/eeg-web`
* `@elata-biosciences/eeg-web-ble`
* Chrome or Edge with Web Bluetooth support
* `https://` or `localhost`
* a Muse 2 or Muse S headband
Safari and iOS do not support this browser BLE workflow. For those platforms, use a native BLE shell, a bridge app, or another transport layer and keep `@elata-biosciences/eeg-web` as your processing boundary.
## Choose the right starting point
Scaffold an EEG BLE demo if you want the fastest path to a reference app.
Learn the browser EEG processing package before adding device transport.
Review the transport package, lifecycle methods, and platform constraints.
Follow the step-by-step version if you want a tutorial before this end-to-end guide.
## Install the packages
```bash theme={null}
pnpm add @elata-biosciences/eeg-web @elata-biosciences/eeg-web-ble
```
```bash theme={null}
npm install @elata-biosciences/eeg-web @elata-biosciences/eeg-web-ble
```
`@elata-biosciences/eeg-web-ble` depends on `@elata-biosciences/eeg-web` for shared frame types and the EEG WASM layer.
## Integration flow
Initialize the EEG WASM module before you create a transport or call analysis helpers.
```ts theme={null}
import { initEegWasm } from "@elata-biosciences/eeg-web";
await initEegWasm();
```
Include an Athena decoder up front so both classic and Athena-compatible headbands work through the same setup.
```ts theme={null}
import { AthenaWasmDecoder } from "@elata-biosciences/eeg-web";
import { BleTransport } from "@elata-biosciences/eeg-web-ble";
const transport = new BleTransport({
sourceName: "my-app",
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
logger: (msg) => console.debug("[BLE]", msg),
},
});
```
Use `onStatus` for UI state and `onFrame` for EEG data.
```ts theme={null}
import type { HeadbandFrameV1 } from "@elata-biosciences/eeg-web";
transport.onStatus = (status) => {
const el = document.getElementById("status");
if (el) el.textContent = status.state;
};
transport.onFrame = (frame: HeadbandFrameV1) => {
console.log("channels", frame.eeg.channelNames);
console.log("rows", frame.eeg.samples.length);
};
```
Browser BLE flows usually need a click or another user gesture to open the device picker.
```ts theme={null}
async function connectHeadband() {
try {
await transport.connect();
await transport.start();
} catch (err) {
console.error("Connection failed:", err);
}
}
```
In simpler app flows, `startStreaming()` is often the safest default because it combines `connect()` and `start()` in one call.
Compute band powers per channel as frames arrive.
```ts theme={null}
import { band_powers } from "@elata-biosciences/eeg-web";
transport.onFrame = (frame: HeadbandFrameV1) => {
const { eeg } = frame;
for (let ch = 0; ch < eeg.channelCount; ch++) {
const channelSamples = eeg.samples.map((row) => row[ch]);
const powers = band_powers(
new Float32Array(channelSamples),
eeg.sampleRateHz,
);
console.log(
`${eeg.channelNames[ch]}: alpha=${powers.alpha.toFixed(2)} beta=${powers.beta.toFixed(2)}`,
);
}
};
```
## Full example
```ts theme={null}
import {
initEegWasm,
band_powers,
AthenaWasmDecoder,
} from "@elata-biosciences/eeg-web";
import type { HeadbandFrameV1 } from "@elata-biosciences/eeg-web";
import { BleTransport } from "@elata-biosciences/eeg-web-ble";
await initEegWasm();
const transport = new BleTransport({
sourceName: "my-app",
deviceOptions: {
athenaDecoderFactory: () => new AthenaWasmDecoder(),
logger: (msg) => console.debug("[BLE]", msg),
},
});
transport.onStatus = (status) => {
const el = document.getElementById("status");
if (el) el.textContent = status.state;
};
transport.onFrame = (frame: HeadbandFrameV1) => {
const { eeg } = frame;
for (let ch = 0; ch < eeg.channelCount; ch++) {
const channelSamples = eeg.samples.map((row) => row[ch]);
const powers = band_powers(
new Float32Array(channelSamples),
eeg.sampleRateHz,
);
console.log(
`${eeg.channelNames[ch]}: alpha=${powers.alpha.toFixed(2)} beta=${powers.beta.toFixed(2)}`,
);
}
};
try {
await transport.connect();
await transport.start();
} catch (err) {
console.error("Connection failed:", err);
}
```
## Clean shutdown
Stop the stream and release the Bluetooth session when the user leaves the view or closes the page.
```ts theme={null}
async function shutdown() {
await transport.stop();
await transport.disconnect();
}
window.addEventListener("beforeunload", shutdown);
```
## Handle reconnection
If a disconnect is recoverable, try to reconnect and restart the stream.
```ts theme={null}
transport.onStatus = (status) => {
if (status.state === "disconnected" && status.recoverable) {
console.log("Attempting reconnect...");
setTimeout(async () => {
try {
await transport.connect();
await transport.start();
} catch (error) {
console.error("Reconnect failed:", error);
}
}, 2000);
}
};
```
## Understand the transport lifecycle
| Method | What it does |
| - | - |
| `startStreaming()` | Connects and starts the stream in one call |
| `connect()` | Opens the Bluetooth picker and prepares the session |
| `start()` | Begins the EEG stream and triggers `onFrame` |
| `stop()` | Stops the stream but keeps the Bluetooth session open |
| `disconnect()` | Releases the Bluetooth session |
## Device notes
Works with the standard browser BLE flow.
Use `athenaDecoderFactory` during transport creation. Athena devices expose richer frame content, including 8 EEG channels plus additional sensor data.
You can also inspect metadata after connecting:
```ts theme={null}
await transport.connect();
const isAthena = transport.getIsAthena();
const boardInfo = transport.getBoardInfo();
const channelNames = transport.getEegNames();
```
If you are testing across Muse variants, keep the Athena decoder factory enabled from day one so you do not need a separate transport setup later.
## Architecture at a glance
The browser EEG BLE flow usually looks like this:
1. initialize `@elata-biosciences/eeg-web`
2. create a `BleTransport`
3. connect from a user gesture
4. receive `HeadbandFrameV1` frames
5. compute app metrics such as band powers, scores, or state transitions
6. render charts, scores, or adaptive UI in your app
## Integration tips
* Buffer frames before analysis for more stable results
* Use `Float32Array` when calling `band_powers`
* Keep BLE connection logic behind explicit user actions
* Move frame handling into app state instead of leaving it in `console.log`
* Start with a demo app if you need a working reference project first
* Test with synthetic data first during development if no headband is available
`band_powers` is most useful with roughly 1 to 2 seconds of data. At 256 Hz, that is about 256 to 512 samples.
## Where to go next
Review device behavior, protocol details, and compatibility notes.
See the transport package API and platform caveats in more detail.
Follow the tutorial version of this workflow with app-oriented steps.
Integrate browser EEG processing into an app before adding BLE transport.
Learn the frame schema and transport boundary used by EEG flows.
Pair this EEG workflow with the browser camera pipeline when you need multimodal biometrics.
# EEG in a browser App
Source: https://docs.elata.bio/sdk/guides/eeg-browser
Add Elata EEG WASM APIs to an existing browser application.
Use this page if you want the package model for browser EEG, not a full integration walkthrough.
If you want numbered steps for an existing app, use [Add EEG To An Existing Browser App](/sdk/tutorials/eeg-existing-app). If you want the scaffold path, use [Build Your First Elata App](/sdk/tutorials/first-app).
If you want a running app without manual wiring, scaffold with [create-elata-demo](/sdk/create-elata-demo) first.
EEG is **optional** in the usual Elata journey: **camera rPPG** is the primary browser integration for many apps. Add `eeg-web` when you need brain-signal analysis; add **`eeg-web-ble`** alongside it when you need **Web Bluetooth** for a Muse-compatible headset.
EEG measures electrical activity from the brain using a wearable headset. In browser apps, developers usually use EEG to stream headset samples, compute features such as band powers, and drive feedback, dashboards, adaptive UX, or interactive experiences.
Concrete app examples include:
* meditation or breath-training apps that adapt guidance to focus or calmness
* neurofeedback experiences that reward attention or steadiness
* simulated athletic-performance or training apps that react to mental state
* games or creative tools that change difficulty, pacing, or effects based on live EEG features
***
## Install
```bash pnpm theme={null}
pnpm add @elata-biosciences/eeg-web
```
```bash npm theme={null}
npm install @elata-biosciences/eeg-web
```
***
## What `eeg-web` Gives You
`@elata-biosciences/eeg-web` provides:
* browser-side EEG WASM initialization
* signal-processing and model exports such as `band_powers`
* shared types and contracts used by higher-level browser integrations
This package does not handle Bluetooth device connection by itself. Add `@elata-biosciences/eeg-web-ble` if you also need browser BLE transport.
***
## Minimal Integration
```ts theme={null}
import { initEegWasm, band_powers } from "@elata-biosciences/eeg-web";
await initEegWasm();
const eegData = new Float32Array([0, 1, 0, -1]);
const powers = band_powers(eegData, 256);
console.log("alpha", powers.alpha);
```
***
## Typical Integration Flow
1. Initialize the packaged WASM runtime with `initEegWasm()`.
2. Pass browser-side EEG sample buffers into the exported analysis functions.
3. If you later need live device transport, combine this package with `eeg-web-ble`.
***
## When To Use The EEG Template Instead
Prefer the scaffolded `eeg-demo` template when you want:
* a known-good Vite setup
* a reference for how bundled WASM assets should be served
* a synthetic-data app that runs without hardware
***
## Common Gotchas
* `band_powers()` takes a **single-channel** `Float32Array`, not `number[]`. `frame.eeg.samples` is laid out as `samples[sampleIdx][channelIdx]` (one row per time step), so extract a channel with `getEegChannelSamples(frame, channelIdx)` from `eeg-web`. Passing a plain array will cause a WASM runtime error. For a Muse headband (4 channels: TP9, AF7, AF8, TP10), use a frontal channel (AF7 = index 1, or AF8 = index 2) for cognitive state features, or average across channels.
* `WasmCalmnessModel.process()` expects **interleaved** samples: `[s0_ch0, s0_ch1, s1_ch0, s1_ch1, ...]`. Use `getEegInterleavedSamples(frame)` from `eeg-web` to get that layout.
* `WasmCalmnessModel` needs `channelCount` at construction, but `channelCount` only arrives on the first frame. Construct the model inside your first-frame handler.
* If `initEegWasm()` fails, your app may not be loading the packaged `wasm/` assets correctly.
* If you need a live headset connection, `eeg-web` alone is not enough.
* If you are only evaluating the SDK, the scaffolded app is faster than manual setup.
***
## Next
Step-by-step integration tutorial
Connect a Muse headset
Package API and exports
Common failures and fixes
# Example Applications
Source: https://docs.elata.bio/sdk/guides/example-apps
Open source browser apps built with Elata EEG, Web Bluetooth, and rPPG packages.
These are full product-shaped integrations with routing, sessions, charts, and game loops. They go beyond the `create-elata-demo` starter templates and show what a complete app looks like.
## Featured Apps
Guided breathing with live EEG and rPPG biometric monitoring.
3D flight simulator with post-session neural analytics.
Brain-reactive arcade gameplay that responds to mental state.
Chess against Stockfish with a neural HUD overlay.
Stress-modulated reaction game with results tracking.
***
## What These Apps Demonstrate
Each app is a standalone repository that uses the published `@elata-biosciences` packages:
| Package | Purpose |
| - | - |
| `@elata-biosciences/eeg-web` | EEG signal processing and models |
| `@elata-biosciences/eeg-web-ble` | Web Bluetooth headband streaming |
| `@elata-biosciences/rppg-web` | Camera-based heart rate measurement |
These apps are useful as reference implementations when you need to see how a real product wires up SDK sessions, state management, charts, and cleanup.
***
## When To Use These vs. `create-elata-demo`
| If you want to... | Start here |
| - | - |
| Get a working app in under a minute | [`create-elata-demo`](/sdk/create-elata-demo) |
| See a complete product with routing, game loops, or data visualization | One of the example apps above |
| Compare your integration against a known-good reference | Either path works |
***
## Next
Scaffold a starter app
Package decision guide
Primary browser integration
# Remote sensing in a browser App
Source: https://docs.elata.bio/sdk/guides/rppg-browser
Add browser-based camera rPPG with the supported Elata session helpers.
Use this page if you want the integration model for browser rPPG.
If you want numbered steps for an existing app, use [Add Camera-Based rPPG To An Existing Browser App](/sdk/tutorials/rppg-existing-app). If you want the scaffold path, use [Build Your First Elata App](/sdk/tutorials/first-app).
This is the **primary** browser biosignal path for most products: no headset required before you ship meaningful signal UX.
rPPG stands for remote photoplethysmography. It estimates pulse-related changes from camera video, usually from a face region, without requiring a wearable sensor.
In browser apps, developers usually use rPPG to turn a live camera stream into heart-rate-style metrics, diagnostics, and wellness-oriented feedback.
Concrete app examples include:
* deception or bluffing games that react to pulse changes during key moments
* stress or arousal feedback in training and social experiences
* breathing and relaxation flows that show physiological response over time
* biofeedback-oriented health or wellness apps that want camera-based pulse signals without extra hardware
***
## Install
```bash pnpm theme={null}
pnpm add @elata-biosciences/rppg-web
```
```bash npm theme={null}
npm install @elata-biosciences/rppg-web
```
***
## Recommended vs. Advanced
Use `createRppgSession()` for browser apps. It handles WASM init, frame capture, ROI orchestration, diagnostics, and cleanup.
**Recommended:**
* Use `createRppgSession()` for browser apps.
* Use `createManagedRppgSession()` when you want built-in restart behavior after a terminal processor failure.
* Use `createRppgPipeline()` from `@elata-biosciences/eeg-web` only if you intentionally need low-level sample ingestion.
**Advanced:**
* Use `RppgProcessor`, `DemoRunner`, custom backends, or generated WASM bindings only if you need custom orchestration and understand the runtime lifecycle already.
* If you are not debugging the SDK itself, do not start with generated WASM exports.
***
## Minimal Integration
```ts theme={null}
import { createRppgSession } from "@elata-biosciences/rppg-web";
const session = await createRppgSession({
video: videoEl,
sampleRate: 30,
backend: "auto",
faceMesh: "off",
onDiagnostics: (diagnostics) => {
console.log(diagnostics.state.status, diagnostics.faceTrackingMode);
console.log(diagnostics.framesSeen, diagnostics.totalSamplesReceived);
console.log(diagnostics.issues, diagnostics.processorFailure);
},
onError: (error) => {
console.error(error.code, error.message);
},
});
console.log(session.getMetrics());
```
***
## Typical Integration Flow
1. Acquire a camera stream in the browser and attach it to a `video` element.
2. Call `createRppgSession({ video, backend: "auto" })`.
3. Read metrics from `session.getMetrics()`.
4. Surface diagnostics from `onDiagnostics` or `session.getDiagnostics()`.
5. Stop the session during cleanup with `await session.stop()`.
`createRppgSession()` owns WASM init, FaceMesh loading, frame scheduling, ROI selection, diagnostics, and cleanup.
Use `session.state` or `diagnostics.state` to distinguish:
* normal `running`
* startup fallback or degraded setup via `degraded`
* terminal runtime processor failure via `failed`
If you intentionally choose `faceMesh: "off"`, the session stays in supported `video_frame` mode and is not reported as a FaceMesh failure by default.
If your app needs explicit asset paths instead of the default `/pkg/*` lookup, pass one or more of:
```ts theme={null}
const session = await createRppgSession({
video: videoEl,
wasmJsUrl: "/assets/rppg_wasm.js",
wasmBinaryUrl: "/assets/rppg_wasm_bg.wasm",
});
```
Advanced apps can also provide `wasmImporter` directly.
***
## Managed Restart Flow
If you want the SDK to own restart timing after terminal processor failures, use the managed wrapper:
```ts theme={null}
import { createManagedRppgSession } from "@elata-biosciences/rppg-web";
const managed = await createManagedRppgSession({
video: videoEl,
faceMesh: "off",
maxRetries: 3,
retryDelayMs: 1500,
onStateChange: (state) => {
console.log(state.status, state.retryCount, state.lastError?.code);
},
});
```
The managed wrapper exposes high-level states such as `starting`, `running`, `retrying`, and `failed`, while still letting apps drop down to the underlying `RppgSession` when needed.
***
## Advanced Helpers
Use `getTraceSnapshot()` when you need recent waveform or debug points for charts, debug panels, or regression capture:
```ts theme={null}
const trace = session.getTraceSnapshot(300);
console.log(trace.points);
console.log(trace.lastSample);
console.log(trace.backendFailure);
```
For peak/threshold waveform debug from the trace data, use `computeTraceWaveformDebug()`:
```ts theme={null}
import { computeTraceWaveformDebug } from "@elata-biosciences/rppg-web";
const waveform = computeTraceWaveformDebug(session.getTraceSnapshot(300));
console.log(waveform.peaks);
```
Use `normalizeRppgError()` instead of parsing raw message text in app code:
```ts theme={null}
import { normalizeRppgError } from "@elata-biosciences/rppg-web";
const normalized = normalizeRppgError(session.lastError, session.getDiagnostics());
console.log(normalized?.code);
console.log(normalized?.message);
console.log(normalized?.guidance);
```
This gives apps stable categories such as `wasm_init_failed`, `backend_unavailable`, `camera_not_playing`, and `processor_failed`.
If you want a single app-facing snapshot with restart status, publish gating, trace data, and stable messages, use `createRppgAppAdapter()`:
```ts theme={null}
import {
createManagedRppgSession,
createRppgAppAdapter,
} from "@elata-biosciences/rppg-web";
const managed = await createManagedRppgSession({
video,
faceMesh: "off",
});
const adapter = createRppgAppAdapter();
const app = adapter.getSnapshot(managed);
if (app.canPublish) {
console.log(app.publishBpm);
}
console.log(app.status, app.message);
```
If you want the SDK to own the recurring snapshot loop too, use `createRppgAppMonitor()`:
```ts theme={null}
import {
createManagedRppgSession,
createRppgAppMonitor,
} from "@elata-biosciences/rppg-web";
const managed = await createManagedRppgSession({
video,
faceMesh: "off",
});
const monitor = createRppgAppMonitor(managed, { intervalMs: 500 });
monitor.subscribe((snapshot) => {
console.log(snapshot.status, snapshot.publishBpm);
});
monitor.start();
```
`createRppgSession()` now waits for the video element to start playing by default. If you need to coordinate that step yourself, call `ensureVideoPlaying()` directly:
```ts theme={null}
import { ensureVideoPlaying } from "@elata-biosciences/rppg-web";
await ensureVideoPlaying(video, { timeoutMs: 5000 });
```
***
## When To Use The rPPG Template Instead
Prefer the scaffolded `rppg-demo` template when you want:
* a known-good browser camera app
* a reference for packaged WASM asset loading
* a faster comparison point when debugging your own integration
***
## Common Gotchas
* If `session.backendMode` is `unavailable`, your app is probably not serving the packaged `pkg/` assets correctly.
* If `session.state.status` is `failed`, treat that processor backend as terminal and recreate the session instead of continuing to poll metrics from it.
* If you see "backend pipeline has no push\_sample API", you likely bypassed the safe wrapper path. Start with `createRppgSession()` for browser apps, or `initEegWasm()` plus `createRppgPipeline()` for low-level ingestion.
* If you hit `wasmrppgpipeline_new`, initialize the WASM module before creating low-level pipelines and avoid calling generated constructors directly.
* If you see deprecated init warnings, route startup through `initEegWasm()` instead of forwarding raw strings, URLs, or buffers to the generated init exports.
* If camera access fails, confirm the page has permission to use `getUserMedia`.
* If `session.lastError` is non-null, use its `code` and `message` to surface the real capture or processor failure instead of retrying blindly.
* If you are just evaluating the SDK, the scaffolded app is much faster than building the whole browser pipeline yourself.
***
## Version Guidance
The packages are versioned independently, so `rppg-web` and `eeg-web` won't share a version number, and the latest releases aren't automatically compatible with each other. If you use several Elata packages together, use the set `create-elata-demo` pins. See [Compatibility](/sdk/operations/compatibility#package-versions).
***
## Next
Step-by-step rPPG integration
Package API and exports
Common failures and fixes
# Camera Integration
Source: https://docs.elata.bio/sdk/guides/rppg-camera
Add camera-based heart rate to a browser app with the Elata rPPG Web SDK.
Use this guide when you want lower-level control over camera-based heart rate in a browser app. For the recommended high-level API, start with [rPPG in a browser app](/sdk/guides/rppg-browser).
## What this guide covers
This guide shows how to:
* capture webcam frames in the browser
* detect a face and extract a face region
* feed samples into the rPPG processor
* read live BPM and signal quality
* improve estimates with Muse PPG when available
## Before you start
You need:
* `@elata-biosciences/rppg-web`
* a browser with camera access (`getUserMedia`) and WebAssembly support
* `https://` or `localhost`
```bash npm theme={null}
npm install @elata-biosciences/rppg-web
```
```bash pnpm theme={null}
pnpm add @elata-biosciences/rppg-web
```
rPPG works best when the face is well lit, mostly still, and fully visible in frame.
Most apps should use `createRppgSession()`, which wraps everything on this page (WASM init, frame capture, ROI selection, diagnostics, cleanup). See [rPPG in a browser app](/sdk/guides/rppg-browser). Use the lower-level pieces below only when you need custom orchestration.
## Choose a starting point
Scaffold a working browser app if you want the fastest path to a reference implementation.
Learn the package entry points and the higher-level browser API.
See the lower-level camera and face-detection primitives used in this guide.
Add Muse-based calibration when you want stronger estimates.
## Quick integration with DemoRunner
`DemoRunner` handles frame capture, face detection, ROI extraction, and processing in one flow, while leaving you in control of the processor.
```ts theme={null}
import {
RppgProcessor,
MediaPipeFrameSource,
DemoRunner,
loadWasmBackend,
} from "@elata-biosciences/rppg-web";
const video = document.querySelector("video")!; // a playing camera