# MetaSoul EPU4 API: guide for AI agents and developers

**API base URL:** `https://emohuman.com`  
**Interactive bilingual Swagger and MDAD monitor:** https://emohuman.com/docs/EPU4/  
**Machine-readable specification:** https://emohuman.com/docs/EPU4/openapi.json  
**Channel map:** https://emohuman.com/docs/EPU4/mdad-channels.json  
**Based on:** *EPU SDK - API Integration Guide*, October 19, 2026. Channel mapping is taken from the earlier *EPU-III Brochure*, pages 14–15 (see compatibility note below).

## The simplest workflow

If you do **not** know your EPUID, an account administrator can use the
separately deployed `POST /epuid_list` operation with one MetaSoul **account
secret**. This is read-only inventory discovery and does not log in to any EPU.
It lists both `available` and `in_use` devices when the source provides groups.
The older flat source format is returned as `availability_unknown`.

```http
POST /epuid_list HTTP/1.1
Host: emohuman.com
Content-Type: application/json

{"secret":"YOUR_ACCOUNT_SECRET"}
```

The response is limited to `reply`, `total`, `available`, `in_use`, and
`availability_unknown`; each list contains complete EPUID strings. Never print
the secret or persist raw account responses. This API must be enabled by the
server operator before use; `503 EPUID_LIST_DISABLED` means it has not been
deployed/enabled. An inventory status is **not** proof of exclusive reservation
or device rest readiness. This account-list operation does not use a Bearer
token and cannot replace `POST /user_login` for device-specific operations.

JavaScript SDK helper (use a secure runtime; never put your secret in source):

```js
import { listEpuidsForSecret } from './epuid-list.mjs';
const inventory = await listEpuidsForSecret(process.env.METASOUL_ACCOUNT_SECRET);
// inventory.available, inventory.in_use, inventory.availability_unknown
```

1. Authenticate with `POST https://emohuman.com/user_login` using JSON `{"secret":"...","epuid":"..."}`. Read the returned `token` and `epuid`.
2. To observe changes as they happen, connect a **Socket.IO** client to `https://emohuman.com/<epuid>` and listen for the `mdad` event (`{epuid, mdad: number[]}`). Connect before triggering updates to avoid missing a broadcast.
3. Call `POST https://emohuman.com/derive` with header `Authorization: Bearer <token>` and JSON body **only** `{"text":"You have an amazing voice"}`. This is sufficient to process the text and update MDAD. **Do not send `reasoning` or `action` unless you want extra AI-generated output.**
4. The text-only call normally returns `{"reply":"ok"}`; the updated array arrives through Socket.IO. If needed, `GET https://emohuman.com/mdad` with the same bearer token waits for the *next* MDAD frame for up to approximately 5 seconds. It does not necessarily retrieve the frame sent before the GET request.
5. Decode the received numeric array using **zero-based** channel indexes. The current guide documents **120 values**.

### REST requests

```http
POST /user_login HTTP/1.1
Host: emohuman.com
Content-Type: application/json

{"secret":"YOUR_EPU_SECRET","epuid":"YOUR_EPU_ID"}
```

Response (example): `{"reply":"ok","token":"abc123...","epuid":"your_epu_id"}`

```http
POST /derive HTTP/1.1
Host: emohuman.com
Authorization: Bearer <token>
Content-Type: application/json

{"text":"You have an amazing voice"}
```

**Text-only response:** `{"reply":"ok"}`. The `text` response field is optional and is returned when appropriate for AI reasoning/action modes.

**Optional mode examples:** `{"text":"...","reasoning":true}` or `{"text":"...","action":true}`. The `timezone` string is also optional.

```http
GET /mdad HTTP/1.1
Host: emohuman.com
Authorization: Bearer <token>
```

The documented response has `reply` and `mdad`, with **120 numeric values**. The guide does not define explicit timeout status codes or response structure in error cases.

## Realtime Socket.IO

Socket.IO is not ordinary WebSocket, and it is not a REST endpoint. Use a compatible Socket.IO client.

```javascript
import { io } from 'socket.io-client';
const socket = io('https://emohuman.com/' + encodeURIComponent(epuid));
socket.on('mdad', ({ epuid, mdad }) => {
  console.log('Apex levels:', [4,14,24,34,44,54,64,74,84,94,104,114].map(i => mdad[i]));
});
```

The integration guide says each `/derive` call produces an MDAD broadcast. It does **not** document a separate Socket.IO authentication mechanism. Authenticate REST requests with Bearer tokens; do not put secrets or bearer tokens in public URLs.

## Session lifetime and optional keepalive

The **current gateway implementation** defaults to a **60-minute REST inactivity timeout**. An optional login `duration` (minutes) can change this default. Successful authenticated REST requests update the session's last-used time. Receiving Socket.IO MDAD frames **does not** renew the REST session. The server checks for idle sessions periodically, so release need not occur at the exact second the timeout is reached.

The Bearer token also has a separate **24-hour maximum lifetime**. Requests made after expiration or session invalidation can return HTTP `401` with messages such as `SESSION_EXPIRED`, `SESSION_NOT_FOUND`, or `TOKEN_INVALIDATED`. Obtain a new token by logging in again; do not keep retrying a rejected token. A disconnected or expired REST session and a connected Socket.IO browser transport are different states, so use the **last incoming MDAD frame timestamp** to confirm fresh data.

For a deliberately long-running client, an optional authenticated `GET /mdad` after **55 minutes without another authenticated REST request** can renew the default inactivity period without sending text or generating an emotion:

```http
GET /mdad HTTP/1.1
Host: emohuman.com
Authorization: Bearer <token>
```

This call reads/consumes one recent cached HTTP MDAD frame or waits up to approximately five seconds for the next frame. If the EPU does not send a frame, the request may return `503 EPU_NOT_CONNECTED` even though the Bearer session was validated and renewed. **Do not treat 503 as token expiration.** The live Socket.IO subscription is not consumed by this request.

Do **not** enable silent keepalive for every application by default: an abandoned client could keep an EPUID reserved and prevent another server from acquiring it. For browser use, the Swagger keepalive is opt-in and runs only while the tab is visible. The browser's Disconnect button clears its local authentication state; it does not currently invoke an upstream EPU release request. The 24-hour token limit still applies even with keepalive.

## Decode MDAD: primary emotion APEX values

For the historical channel layout shown in the *EPU-III Brochure*, each of 12 emotion groups uses nine wave subchannels plus one feeling channel. The **APEX** is the fifth wave channel: `4 + 10 * emotionIndex`.

| Emotion (index) | APEX MDAD channel | Access |
|---|---:|---|
| Excite (0) | 4 | `mdad[4]` |
| Confident (1) | 14 | `mdad[14]` |
| Happy (2) | 24 | `mdad[24]` |
| Trust (3) | 34 | `mdad[34]` |
| Desire (4) | 44 | `mdad[44]` |
| Fear (5) | 54 | `mdad[54]` |
| Surprise (6) | 64 | `mdad[64]` |
| Inattention (7) | 74 | `mdad[74]` |
| Sad (8) | 84 | `mdad[84]` |
| Nostalgia (9) | 94 | `mdad[94]` |
| Disgust (10) | 104 | `mdad[104]` |
| Anger (11) | 114 | `mdad[114]` |

**Optimistic feeling** is **channel 9**, which is *not* Excite APEX channel 4.

```javascript
function decodeMdad(mdad) {
  if (!Array.isArray(mdad) || mdad.length < 120 ||
      !mdad.every(x => typeof x === 'number' && Number.isFinite(x))) {
    throw new Error('Expected at least 120 numeric MDAD values');
  }
  const names = ['Excite','Confident','Happy','Trust','Desire','Fear',
    'Surprise','Inattention','Sad','Nostalgia','Disgust','Anger'];
  // A reading outside 0..100 is an invalid level, not a level to clamp.
  // Keep the original `mdad` array unchanged for diagnostics.
  const interpretedIndexes = [9, ...names.map((_, i) => 4 + 10*i), 121, 122];
  const invalidLevels = interpretedIndexes
    .filter(index => index < mdad.length && (mdad[index] < 0 || mdad[index] > 100))
    .map(index => ({ index, raw: mdad[index] }));
  const level = index => mdad.length > index && mdad[index] >= 0 && mdad[index] <= 100
    ? mdad[index] : null;
  const apex = Object.fromEntries(names.map((name, i) => [name, level(4 + 10*i)]));
  return {
    apex,
    optimistic: level(9),
    pulseSpeed: mdad.length > 120 ? mdad[120] : null,
    frustrationSatisfaction: level(121),
    painPleasure: level(122),
    temperature: mdad.length > 123 ? mdad[123] : null,
    invalidLevels,
  };
}
```

### Additional channels are **not** guaranteed in EPU4

The earlier *EPU-III Brochure* (p. 15) describes a **124-channel** packet. The following interpretations for 121 and 122 reflect the operator's current EPU4 observations; the older brochure gave **the opposite direction for channel 121**:

- `mdad[120]`: Pulse speed (0 slower, 50 normal, 100 faster).
- `mdad[121]`: Frustration / Satisfaction (**0 maximum satisfaction / relief of frustration, 50 neutral, 100 maximum frustration**). Values below 50 indicate relief of frustration.
- `mdad[122]`: Pain / Pleasure (**0 maximum pleasure / relief of pain, 50 neutral, 100 maximum pain**). Values below 50 indicate relief of pain.
- `mdad[123]`: Temperature (historical range −40 to +85 °C).

**Do not assume indexes 120–123 exist** in every EPU4 payload. The October 19, 2026 EPU SDK guide specifies **120** MDAD values, so these extra indexes are out of range unless the *actual* response has more than 120 items. The channel names, apex positions, and Optimistic mapping above come from the older brochure and the current MetaSoul Swagger dashboard, not from a new explicit channel table in the EPU4 API guide. A live `GET /mdad` on October 31, 2026 returned **123 values** (indexes 0–122), and separate read-only observations found `50, 50, 50` at indexes 120–122. These support the neutral midpoint, but not the endpoint polarity; the current interpretation for channel 121 comes from operator testing. Index 123 was unavailable. Verify EPU4 firmware compatibility and actual array length for every response. Treat raw values outside 0–100 on the 12 APEX channels, Optimistic, and channels 121–122 as invalid readings, not as saturated endpoints. Preserve the raw payload and exclude invalid levels from persona decisions.

## Operational guidance for agents

- Retrieve the OpenAPI specification for **operation definitions and JSON schemas**; use `mdad-channels.json` for decoder metadata and this guide for Socket.IO instructions.
- Authenticate before calling protected REST operations. Keep secrets and tokens out of logs, generated pages, URLs, and code repositories.
- The October 19 source guide does not specify the token lifetime or all error codes. The **deployed gateway behavior** is described under [Session lifetime and optional keepalive](#session-lifetime-and-optional-keepalive); do not mistake the source guide's omissions for an unlimited session.
- If the MDAD stream stops, inspect Socket.IO connection status and new frame timestamps instead of assuming the last reading is current.
- `GET /mdad` waits for the next frame; plan for a timeout or delayed frame. Do not assume a completed `/derive` guarantees that a *later* GET will return the same frame.
- The service renewal / purchase page is https://metasoul.one/product/cloud-service-epu/ .

## Runnable examples

- [JavaScript / Node example](javascript-example.mjs): Socket.IO subscribe, authenticate, send text, decode incoming MDAD.
- [Python example](python-example.py): authenticate, call `/derive` and `/mdad`, and decode MDAD when available.
- [Bilingual interactive Swagger](index.html): manual testing, graphs, and translation toggle.
