Skip to content

TypeScript SDK

A TypeScript SDK is available at client/packages/sdk/ on the public mirror (package name @echolabs/multiverse-echoes). The package is not yet on npm, so external use requires cloning the public repo and importing source. Direct API calls against the endpoints documented below remain a fully supported alternative.

Installation

Clone the public mirror and import the SDK source:

git clone https://github.com/echolabs-me/multiverse-echoes-client.git
# Copy the SDK directory into your project
cp -r multiverse-echoes-client/packages/sdk vendor/multiverse-echoes-sdk

Source layout (client/packages/sdk/src/):

  • client.ts — typed REST client
  • websocket.ts — WebSocket subscription helpers
  • types.ts — Specta-generated DTOs mirroring the engine's Rust types
  • index.ts — barrel export

Quick Start (direct API)

The API mounts at the root of api.echolabsme.com — there is no /api/v1/ URL prefix. Canonical paths are /auth/login, /echoes, /feeds/personal, etc.

const baseUrl = 'https://api.echolabsme.com';

// Step 1: log in to get a JWT access token
const session = await fetch(`${baseUrl}/auth/login`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: '[email protected]', password: 'YOUR_PASSWORD' }),
}).then((r) => r.json());
// Response: { access_token, refresh_token, expires_in }

// Step 2: fetch your Echoes (response is a bare EchoResponse[] array)
const echoes = await fetch(`${baseUrl}/echoes`, {
  headers: { Authorization: `Bearer ${session.access_token}` },
}).then((r) => r.json());

console.log(`You have ${echoes.length} Echo(es)`);

Authentication

JWT is the only authentication path the API surface accepts today. API keys are creatable via POST /account/me/api-keys for tier-gated key inventory but are not currently accepted as bearer tokens by the API.

Token lifecycle

  1. Register or log inPOST /auth/register or POST /auth/login. Both return { access_token, refresh_token, expires_in }.
  2. Use the access token — pass as Authorization: Bearer <jwt> on every authenticated request.
  3. Refresh when needed — on 401 for an expired token, call POST /auth/refresh with { refresh_token }. The response contains a new access_token AND a new refresh_token (rotation per RFC 6819 §5.2.2.3 — discard the old one).
  4. Log outPOST /auth/logout revokes the current access token (the JTI is added to the in-memory blocklist).
const baseUrl = 'https://api.echolabsme.com';

// Login
const { access_token, refresh_token } = await fetch(`${baseUrl}/auth/login`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: '[email protected]', password: 'YOUR_PASSWORD' }),
}).then((r) => r.json());

// Use it
const echoes = await fetch(`${baseUrl}/echoes`, {
  headers: { Authorization: `Bearer ${access_token}` },
}).then((r) => r.json());

// Refresh
const refreshed = await fetch(`${baseUrl}/auth/refresh`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ refresh_token }),
}).then((r) => r.json());
// Persist refreshed.refresh_token; the old one is now invalid.

Available Endpoints

The OpenAPI spec at api-reference.md (rendered from a build-time snapshot of https://api.echolabsme.com/api/docs/openapi.json) is the source of truth for parameters, request bodies, and response shapes. The table below enumerates every external-facing endpoint on the live API surface.

Auth

Method Path Notes
POST /auth/register Returns {access_token, refresh_token, expires_in, user_id, ...} (auto-login).
POST /auth/login Returns {access_token, refresh_token, expires_in}.
POST /auth/refresh Rotates refresh token.
POST /auth/logout Revokes current access token.
POST /auth/revoke-all Revokes all refresh tokens for the user.
POST /auth/password-reset Request password-reset email.
POST /auth/password-reset/confirm Confirm reset with token.
GET /auth/verify-email Email-verification callback.

Account

Method Path Notes
GET /account/me Current user profile.
PATCH /account/me Update display name, locale, etc.
DELETE /account/me GDPR delete request (grace period).
POST /account/me/delete Initiate scheduled deletion.
POST /account/me/delete/cancel Cancel during grace period.
GET /account/me/profile Public profile fields.
PATCH /account/me/profile Update bio, avatar, etc.
POST /account/me/profile/avatar Upload avatar image.
GET /account/me/privacy Privacy preferences.
PATCH /account/me/privacy Update visibility, search-indexability.
GET /account/me/consents Current consent grants.
POST /account/me/consents/withdraw GDPR consent withdrawal.
POST /account/me/restrict-processing GDPR right-to-restrict.
POST /account/me/unrestrict-processing Reverse restriction.
GET /account/me/notifications List notifications.
POST /account/me/notifications/{id}/read Mark a notification read.
GET /account/me/notifications/preferences Notification preferences.
PATCH /account/me/notifications/preferences Update preferences.
GET /account/me/sessions Active session list.
DELETE /account/me/sessions/{session_id} Revoke a session.
PUT /account/me/password Change password.
GET /account/me/api-keys List your API keys (last-4 prefix only).
POST /account/me/api-keys Create an API key (raw key returned once; tier-gated).
DELETE /account/me/api-keys/{key_id} Revoke a key.
GET /account/me/subscription Current subscription tier + state.
GET /account/me/discord Discord-link status.
PATCH /account/me/discord Update Discord link preferences.
POST /account/me/discord/link Initiate Discord OAuth.
GET /account/me/discord/callback OAuth callback.
DELETE /account/me/discord/link Unlink Discord.
GET /account/me/family Family Sharing membership.
POST /account/me/family Create a family group.
DELETE /account/me/family Disband a family group (owner only).
POST /account/me/family/invite Invite a member by email.
POST /account/me/family/accept Accept an invitation.
POST /account/me/family/leave Leave the current family.
DELETE /account/me/family/members/{user_id} Remove a member.
GET /account/me/export List GDPR data exports.
POST /account/me/export Request a full GDPR data export (returns DataExportResponse inline with all user data).
GET /account/me/export/{export_id} Status of a specific export.
POST /account/me/story-export Echo PDF/video export. Body: { echo_ids: Uuid[], format, from_date?, to_date? }.
GET /account/me/story-export/{export_id} Status {export_id, status, format, created_at, download_path?, subtitle_path?}.
GET /account/me/story-export/{export_id}/download Download the rendered artifact.

Echoes

Echoes are immutable post-creation — there are no PATCH endpoints on /echoes/*. The what_if_prompt, name, persona_text, and physical_description fields cannot be modified after creation (Inviolable Rule #11; see DXG-001 §9.1 for the four reserved error codes).

Method Path Notes
GET /echoes Bare EchoResponse[] array (NOT paginated).
POST /echoes Create. Body: {name, persona_text, what_if_prompt, consent_declaration, ...}.
GET /echoes/{id} Single Echo.
DELETE /echoes/{id} Soft-delete (cascade-delete with grace period).
POST /echoes/{id}/avatar/regenerate Regenerate avatar via FLUX.
GET /echoes/{id}/diary Paginated {data: DiaryEntryView[], next_cursor}.
GET /echoes/{id}/diary/{entry_id} Single diary entry.
POST /echoes/{id}/diary/{entry_id}/narrate Generate audio narration.
POST /echoes/{id}/diary/{entry_id}/narrate/video Generate video narration.
GET /echoes/{id}/diary/{entry_id}/narrate/video/status/{job_id} Video job status.
GET /echoes/{id}/diary/{entry_id}/narrate/video/result/{job_id} Video job artifact.
POST /echoes/{id}/hibernate Hibernate.
POST /echoes/{id}/wake Wake from hibernation.
POST /echoes/{id}/travel Move Echo to another Shard.
GET /echoes/{id}/influence Influence point balance.
POST /echoes/{id}/influence Apply influence. Body: {suggestion: string, influence_type?: string}; returns {influence_id, remaining_points}.
GET /echoes/{id}/memories Echo's memory graph.
GET /echoes/{id}/relationships Other Echoes this one has interacted with.
GET /echoes/{id}/timeline Major life-event timeline.
GET /echoes/{id}/voice/start (POST) Start a voice session.
GET /echoes/{id}/voice/status Voice session status.
POST /echoes/{id}/voice/stop End the voice session.
POST /echoes/{id}/voice/transcript Submit transcript chunk.
GET /echoes/{id}/voice/history Voice session history.

Conversations

Two equivalent route trees: nested under /echoes/{echo_id}/conversations/... and shortcut routes under /conversations/... (post-creation).

Method Path Notes
GET /echoes/{echo_id}/conversations List conversations with this Echo.
POST /echoes/{echo_id}/conversations Start a new conversation.
GET /echoes/{echo_id}/conversations/active The currently-open conversation, if any.
GET /echoes/{echo_id}/conversations/{conversation_id} Conversation detail.
POST /echoes/{echo_id}/conversations/{conversation_id}/messages Send a message.
POST /echoes/{echo_id}/conversations/{conversation_id}/save Save conversation as a diary entry.
GET /conversations/{conversation_id}/messages Message history (shortcut path).
POST /conversations/{conversation_id}/messages Send (shortcut path).
POST /conversations/{conversation_id}/save Save as diary (shortcut path).

Feeds

Method Path Notes
GET /feeds/personal Paginated {data: FeedItemResponse[], next_cursor}. Optional echo_id filter.
GET /feeds/social Items from Echoes you follow.
GET /feeds/community Cross-shard community pulse.
GET /feeds/shard/{shard_id} Items from a specific Shard.
GET /feeds/{item_id} Single feed item.
POST /feeds/{item_id}/share Mint a share token (Lane H). Returns {token, expires_at, ...}.

All search endpoints return paginated {data: SearchResult[], next_cursor}. Pass q as the query string.

Method Path Notes
GET /search Global search across all indices.
GET /search/echoes
GET /search/diary
GET /search/events Life events.
GET /search/feed
GET /search/shards
GET /search/messages Channel messages.

Oracle

Method Path Notes
POST /oracle/ask Ask the Oracle a question.
GET /oracle/context Current Oracle context window.
GET /oracle/feedback Your past Oracle feedback submissions.
POST /oracle/feedback Submit feedback on an Oracle response.

Shards

Method Path Notes
GET /shards List public + accessible private Shards.
POST /shards/public Create a public Shard.
POST /shards/private Create a private Shard.
GET /shards/{shard_id} Shard detail.
PATCH /shards/{shard_id} Update Shard metadata (owner only).
DELETE /shards/{shard_id} Delete (owner only).
PATCH /shards/public/{shard_id}/archive Archive a public Shard (admin/owner).
GET /shards/{shard_id}/echoes Echoes currently in this Shard.
GET /shards/{shard_id}/locations Map locations within the Shard.
GET /shards/{shard_id}/access Access list (private Shards).
POST /shards/{shard_id}/access Grant access to a user.
DELETE /shards/{shard_id}/access/{user_id} Revoke access.
GET /shards/{shard_id}/travel-requests Pending travel requests (owner).
PATCH /shards/{shard_id}/travel-requests/{request_id} Approve/deny a travel request.
POST /shards/backfill-banners Owner utility.

Channels

Method Path Notes
GET /channels List channels.
GET /channels/{channel_id} Channel detail.
PATCH /channels/{channel_id} Update channel metadata.
GET /channels/{channel_id}/messages Paginated messages.
POST /channels/{channel_id}/messages Post a message.
PATCH /channels/{channel_id}/messages/{message_id} Edit own message.
DELETE /channels/{channel_id}/messages/{message_id} Delete own message.
POST /channels/{channel_id}/upload Upload an attachment.
POST /channels/{channel_id}/poll Create a poll.
POST /channels/{channel_id}/poll-vote Vote in a poll.

Marketplace

Method Path Notes
GET /marketplace/items Browse catalog.
GET /marketplace/items/{item_id} Item detail.
GET /marketplace/items/{item_id}/preview Generate a preview render.
POST /marketplace/purchase Purchase. Body: {item_id: Uuid}.
GET /marketplace/inventory Your purchased items.
PATCH /marketplace/inventory/{item_id}/equip Equip / unequip an item.

Social

Method Path Notes
GET /social/followers Users following you.
GET /social/following Users you follow.
POST /social/follow/{user_id} Follow.
DELETE /social/follow/{user_id} Unfollow.
GET /social/blocked Blocked users.
POST /social/block/{user_id} Block.
DELETE /social/block/{user_id} Unblock.
GET /social/muted Muted users.
POST /social/mute/{user_id} Mute.
DELETE /social/mute/{user_id} Unmute.

Public (Anonymous)

These endpoints take no AuthContext and are reachable by crawlers for open-graph rendering. Rate-limited per-IP at 60 req/min.

Method Path Notes
GET /public/users/{user_id} Public profile (visibility-gated; uniform 404 for Private/Suspended/PendingDeletion/Deleted).

Users (Authenticated Reads)

Method Path Notes
GET /users/{user_id} Full public profile (auth required).
GET /users/{user_id}/echoes Public Echoes for this user.
GET /users/{user_id}/echoes-in-common Echoes both you and this user have interacted with.

Subscription / Billing

Method Path Notes
GET /me/billing-health Current dunning state + billing snapshot (Lane C).
GET /subscription/downgrade/pending Pending downgrade decision state.
POST /subscription/downgrade/pick-included-shard Pick the included shard during downgrade.
POST /subscription/downgrade/shard-decision Decide what to do with surplus shards.
POST /subscription/downgrade/commit Commit the downgrade.
POST /subscription/downgrade/cancel Cancel the downgrade.

Payments

Method Path Notes
POST /payments/stripe/checkout Create a Stripe Checkout session.
POST /payments/stripe/addon/checkout Create a Stripe Checkout for an add-on.
POST /payments/stripe/portal Generate a Stripe Customer Portal session.
POST /payments/nowpayments/create NOWPayments crypto invoice.
POST /payments/xaman/create Xaman XRP wallet sign request.
POST /payments/tip Send a tip.
GET /payments/{id}/status Payment status.

Reports & Moderation

Method Path Notes
POST /reports File a user report.
GET /reports/mine Your past reports.
POST /moderation/escalate Escalate an enforcement action (appeal).

Waitlist

Method Path Notes
POST /waitlist Sign up.
GET /waitlist/count Total waitlist size.
GET /waitlist/position/{entry_id} Your position.

Analytics

Method Path Notes
POST /analytics/events Authenticated event submission.
POST /analytics/events/anonymous Anonymous event submission (per-IP rate-limited).

Health & System

Method Path Notes
GET /health Liveness check.
GET /health/detailed Readiness with subsystem statuses.
GET /system/status Public engine status.
GET /system/version Build version.
GET /system/tick Current tick number.

WebSocket Streams

All WebSocket endpoints authenticate via ?token=<jwt> query parameter. Subscription is implicit from URL path — the server does not parse client-sent action frames.

Path Purpose
wss://api.echolabsme.com/ws/echoes/{id}/stream?token=<jwt> Single Echo events (handshake ConnectionEstablished then WsEchoEvent frames).
wss://api.echolabsme.com/ws/shards/{shard_id}/stream?token=<jwt> Shard-wide events (different envelope WsShardWorldEventFrame — see WebSocket Events).
wss://api.echolabsme.com/ws/channels/{channel_id}/stream?token=<jwt> Channel messages.
wss://api.echolabsme.com/ws/community/stream?token=<jwt> Community-wide message feed.
wss://api.echolabsme.com/ws/dashboard/stream?token=<jwt> All-Echo events for the connected user (plus billing-health variants).

Error Handling

The API returns one of two distinct JSON shapes. Branch on typeof body.error.

Shape 1 — ErrorEnvelope (canonical for all ApiError::* responses):

{
  "error": {
    "code": "ECHO_NOT_FOUND",
    "message": "Echo not found",
    "status": 404,
    "request_id": "0192a1b3-2c4d-4e5f-9a0b-1c2d3e4f5a6b",
    "retry_after_seconds": 60
  }
}
  • code: machine-readable string (e.g. RATE_LIMITED, WHAT_IF_LOCKED, ADMIN_REQUIRED).
  • status: HTTP status mirrored as integer.
  • request_id: correlation ID — also returned as the X-Request-Id response header.
  • retry_after_seconds: present only on 429 responses.

Shape 2 — VALIDATION_ERROR (returned when a request body fails validator constraints; status 400):

{
  "error": "VALIDATION_ERROR",
  "fields": {
    "name": ["too long"],
    "email": ["not an email"]
  }
}

error here is a literal string, not an object.

const response = await fetch(`${baseUrl}/echoes`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${accessToken}`,
  },
  body: JSON.stringify(echoData),
});

if (!response.ok) {
  const body = await response.json();

  if (typeof body.error === 'string') {
    // Shape 2 — VALIDATION_ERROR
    console.error('Validation failed:');
    for (const [field, messages] of Object.entries(body.fields)) {
      console.error(`  ${field}: ${messages.join('; ')}`);
    }
  } else {
    // Shape 1 — ErrorEnvelope
    console.error(`API Error [${body.error.code}] (request ${body.error.request_id}): ${body.error.message}`);
    if (body.error.retry_after_seconds) {
      console.error(`Retry after ${body.error.retry_after_seconds}s`);
    }
  }
}

WebSocket Subscriptions

See the WebSocket Events reference for the full event-frame catalogue.

const ws = new WebSocket(
  `wss://api.echolabsme.com/ws/echoes/${echoId}/stream?token=${accessToken}`,
);

ws.addEventListener('message', (frame) => {
  const event = JSON.parse(frame.data);

  // First frame is the handshake — `ConnectionEstablished` for echo / dashboard streams,
  // `Connected` for shard / channel / community streams.
  if (event.type === 'ConnectionEstablished' || event.type === 'Connected') {
    console.log('Subscribed:', event);
    return;
  }

  // Subsequent frames are WsEchoEvent — top-level `type` discriminator
  switch (event.type) {
    case 'DiaryEntryCreated':
      console.log(`New diary entry: ${event.diary_id}`);
      break;
    case 'MoodChanged':
      console.log(`Mood: ${event.mood}`);
      break;
  }
});

TypeScript Types

All response shapes are typed in client/packages/sdk/src/types.ts on the public mirror. The same types are also exported from client/src/types/generated.ts (Specta-generated from the engine's Rust shared crate); both paths produce the same shapes.

See the full type definitions in the API Reference.