Skip to content

API Changelog

All notable platform changes since v0.5.0. Lane numbers reference the internal Lane / Commit identifiers used in commit messages and docs/claude/current-state.md; squash-merge SHAs are listed for traceability.

v0.29.0 — R178, R216 — the shared-shard notice is acknowledged before an Echo first enters a shared shard (October 2026)

Added

  • POST /account/me/shared-shard-notice records that the user has read the notice shown before their Echo first enters a shared (Public or Private) shard. It answers 204. A repeat also answers 204 and keeps the first acknowledgment's time. It answers 409 ACCOUNT_FENCED when the account is being deleted.
  • shared_shard_notice_acknowledged_at is a new field on GET and PATCH /account/me/privacy: the acknowledgment's time in RFC 3339, or null until the user acknowledges the notice.

Changed

  • 409 SHARED_SHARD_NOTICE_REQUIRED from POST /echoes when shard_id names a Public shard, and from POST /echoes/{id}/travel when the destination is a Public or Private shard, until the user has acknowledged the notice. The refusal writes nothing and publishes no event. Creating an Echo in the user's Personal shard, and travelling into it, are never refused this way.
  • POST /echoes now reads and checks the shard shard_id names before it translates the request's text, so an unknown shard (404) or one that is not Public (400 SHARD_NOT_PUBLIC) is refused before anything is stored.

v0.28.0 — R218 — a message relayed from Discord names its author (October 2026)

Changed

A message relayed from Discord by someone without a linked account is still stored under the nil author_id, but it now names its author.

  • external_author_name is a new field on each message GET /channels/{channel_id}/messages returns. It holds the Discord name of the author of such a relay, and is null for every other message.
  • When it is set, author_display_name is that name. It used to be "Unknown".
  • Such a relay's content is the text alone. It used to be Discord | <name>: <text>. Relays stored before this change keep their old content and have no external_author_name.
  • A relay from someone with a linked account is unchanged.

v0.27.0 — R243, R244 — a replaced avatar is never lost (October 2026)

Changed

POST /account/me/profile/avatar stores each upload under a name of its own, avatar-<id>.<ext>, where <id> is new for each upload. So avatar_url changes with every upload, and a browser or cache never shows the picture it replaced. It used to be /images/<user_id>/avatar.<ext> every time.

The new file is written, then the new avatar_url is stored in one step that returns the URL it replaced, and only the avatar that URL names is deleted. The route used to delete the old file first. Two uploads by one user at once each delete the other's avatar or the one before both, never the one the final avatar_url names.

  • 503 IMAGE_STORAGE_UNAVAILABLE when the new file cannot be written. The existing avatar and its avatar_url are unchanged. It used to be lost.
  • When the new avatar_url cannot be stored, the route answers that failure, deletes the new file, and leaves the existing avatar and avatar_url unchanged. 409 ACCOUNT_FENCED when the account is being deleted, and 404 NOT_FOUND when it no longer exists, the same way.
  • Deleting the replaced avatar is best-effort. When it fails, the upload still succeeds with the new avatar_url, and the old file stays until the account is deleted. It used to answer 503 before anything was written.
  • A profile, billing or admin save no longer writes avatar_url, so one built from an earlier read cannot store back an avatar that has been replaced.

v0.26.0 — R215, R234, R238, R239, R240, R241 — a request body the API cannot read answers in the error envelope (October 2026)

Changed

Every route that takes a JSON body answers a body it cannot read in the error envelope, with a code. It used to answer in axum's plain text. Each status is unchanged. The message is the parser's own text, which names the field or position.

  • 400 INVALID_JSON when the body is not JSON.
  • 415 UNSUPPORTED_MEDIA_TYPE when the request has no JSON content type.
  • 422 VALIDATION_INVALID_BODY when the body is JSON of the wrong shape.
  • 413 PAYLOAD_TOO_LARGE when the body is over the route's limit.
  • 400 UNREADABLE_BODY when the body cannot be read at all, as when the connection drops while it is sent.

A body of the right shape whose field fails validation still answers 400 VALIDATION_ERROR with fields. Every JSON route's OpenAPI entry now declares 400, 413, 415 and 422.

POST /channels/{channel_id}/upload and POST /account/me/profile/avatar answer a request whose content type is not multipart/form-data, or whose boundary is missing or bad, 400 INVALID_MULTIPART in the error envelope. It used to answer 400 in plain text (Invalid `boundary` for `multipart/form-data` request).

POST /account/me/profile/avatar answers each failure with its own status and code, as the channel upload does since v0.25.0, and stores nothing when it refuses an upload.

  • 415 UNSUPPORTED_IMAGE_TYPE for a file whose bytes are not a PNG, JPEG or WebP image. It used to answer 400 INVALID_TYPE.
  • 400 INVALID_MULTIPART for a multipart body the server cannot read, wherever in the body it fails. A body that failed before its file was read used to answer 400 NO_FILE, and one that failed while the file was read answered 400 UPLOAD_FAILED.
  • 413 PAYLOAD_TOO_LARGE for an image over 2 MB, or a body over the route's limit. The route now accepts images up to 2 MB. It used to refuse any body over 1 MB with 400 UPLOAD_FAILED, so a portrait between 1 MB and 2 MB could not be uploaded, and its 400 FILE_TOO_LARGE for an image over 2 MB was never sent.

INVALID_TYPE, FILE_TOO_LARGE and UPLOAD_FAILED are no longer sent by this route. EMPTY_FILE still answers an empty file, and NO_FILE a readable body with no file part.

Both upload routes, POST /account/me/profile/avatar and POST /channels/{channel_id}/upload, decide what a file is from its bytes, not from the content type the request declares for it. Each accepts its own formats: the avatar accepts PNG, JPEG and WebP, and the channel upload accepts PNG, JPEG, WebP and GIF, the formats the app's image pickers offer.

  • A file whose bytes are not a format its route accepts answers 415 UNSUPPORTED_IMAGE_TYPE, whatever type it declares. A GIF avatar is refused this way.
  • A file whose declared type names a different format from its bytes answers 415 UNSUPPORTED_IMAGE_TYPE.
  • A file with no declared type is what its bytes say. The avatar route used to take it as PNG.
  • The avatar is stored with the extension its bytes give, and the channel upload sends Discord the content type its bytes give. A refused avatar upload leaves the existing avatar in place.
  • The channel upload used to accept any declared image/* type and send it on as declared. It now accepts only its four formats, and sends a GIF as image/gif.

The four provider webhooks, POST /webhooks/github, POST /webhooks/stripe, POST /webhooks/nowpayments and POST /webhooks/xaman, answer a body they cannot read in the error envelope: 413 PAYLOAD_TOO_LARGE when it is over the limit, and 400 UNREADABLE_BODY when it cannot be read at all. They used to answer the same statuses in plain text. Each webhook still checks its signature against the exact bytes it received.

v0.25.0 — R221, R233 — the channel image upload refuses each failure by name, and accepts images up to 5 MB (October 2026)

Changed

POST /channels/{channel_id}/upload answers each failure with its own status and code, in the error envelope, and stores no message when it refuses an upload.

  • 415 UNSUPPORTED_IMAGE_TYPE for a file that is not an image. It used to answer 400 INVALID_TYPE. Nothing is sent to Discord.
  • 400 INVALID_MULTIPART for a multipart body the server cannot read, wherever in the body it fails. A body that failed before its file was read used to answer 400 NO_FILE, and one that failed while the file was read answered 400 UPLOAD_FAILED.
  • 413 PAYLOAD_TOO_LARGE for an image over 5 MB, or a body over the route's limit. The route now accepts images up to 5 MB. It used to refuse any body over 1 MB with 400 UPLOAD_FAILED, so its 400 FILE_TOO_LARGE for an image over 5 MB was never sent.
  • 502 IMAGE_UPLOAD_FAILED when Discord's reply carries no image. The route used to store a message with an empty image address and answer 200.

UPLOAD_FAILED, FILE_TOO_LARGE and INVALID_TYPE are no longer sent by this route. NO_FILE still answers a readable body with no file part.

v0.24.0 — R224 — a life event has one feed item, under the event's id (October 2026)

Changed

  • A life_event feed item's item_id is now the life event's own id. It used to be an id the feed item was given for itself. This holds wherever a feed item is sent: GET /feeds/personal, GET /feeds/shard/{shard_id}, GET /feeds/social, GET /feeds/community and GET /feeds/{item_id}.
  • For a user whose locale is not English, a life_event feed item now comes back with its translated title and body and with content_locale set to that locale, once the translation is stored. It used to come back in English with content_locale: "en" every time: the translation is stored under the life event's id, which the feed item did not carry.
  • On the Postgres store, a life event's is_public, significance and owner are what they were: private until its owner shares it, 800, and owned by its Echo's owner. Before this version, a server on the earlier storage engine also wrote a second copy of each life event's feed item, with no owner, public, at significance 500. That copy is no longer written. No route, request or response shape changes, and feed items stored before this version keep their ids.

v0.23.0 — R222 — search results name their kind in one vocabulary (September 2026)

Changed

  • SearchResult.item_type is one of six values: echo, shard, diary, event, message and feed. GET /search/echoes, /search/shards, /search/diary, /search/events and /search/messages send the first five, as before.
  • GET /search/feed, GET /search?type=feed, and the feed results the global GET /search merges, now send item_type: "feed". They used to send the feed item's kind in a second spelling, such as DiaryEntry or LifeEvent. item_type names the kind of search that found the result. An item_id alone does not tell the kinds apart: /search/diary and /search/events send feed item ids too.

Added

  • SearchResult.feed_item_type: on a feed result, the feed item's kind, spelled as the feed endpoints spell it (diary_entry, life_event, relationship_change, achievement, global_event_participation, user_shared). On every other result it is null.

Added

  • PATCH /account/me/privacy accepts community_opt_out, and GET /account/me/privacy and the PATCH's response return it. It is false by default: community features rest on legitimate interest, and no consent is asked for them.
  • While community_opt_out is on:
  • the user's POST /channels/{channel_id}/messages, PATCH /channels/{channel_id}/messages/{message_id}, POST /channels/{channel_id}/upload, POST /channels/{channel_id}/poll, POST /channels/{channel_id}/poll-vote and POST /feeds/{item_id}/share are refused with 403 Forbidden and the new code COMMUNITY_OPTED_OUT, before anything is written;
  • POST /social/follow/{user_id} naming the user is refused with 403 Forbidden and COMMUNITY_OPTED_OUT;
  • the user's messages from a linked Discord account are not relayed;
  • other users' GET /feeds/social, GET /feeds/shard/{shard_id} and GET /feeds/community leave out the user's items, and GET /feeds/{item_id} answers 404 Not Found to them for such an item. The user still sees their own items, and GET /feeds/personal is unchanged.
  • Turning community_opt_out on stores it, waits for the user's community actions already under way to finish, and then removes the user's name from their existing channel messages and from their share pages. Turning it off does not restore it. Every request that turns it on runs the removal, even when it was on already, and the setting stays stored whatever the removal does:
  • if the actions under way outlast the wait, the PATCH answers 503 Service Unavailable with the new code COMMUNITY_OPT_OUT_PENDING, and no name is removed yet;
  • if a write of the removal fails, the PATCH answers 500 Internal Server Error;
  • either way, a PATCH that turns it on again finishes the removal, and the server also retries it on its own, hourly by default.
  • GET /account/me/privacy and the PATCH's response carry community_opt_out_cleanup_pending. It is true from the moment community_opt_out is turned on until the removal of the name completes, so it stays true after a 503 or a 500. Turning community_opt_out off sets it false and cancels a removal that has not begun; a removal that has begun finishes first, and the names it took stay removed. A PATCH that turned the setting on and whose removal was cancelled this way answers 200 OK with the settings as they then stand, so its community_opt_out can be false.
  • The channel and community WebSocket streams carry a new event, {"type": "ChannelMessagesAnonymised", "channel_ids": [...]}, when a user's messages are anonymised by the opt-out or by account deletion, so an open view of those channels can reload. It never names the user. The channel stream names only its own channel, and the community stream never names a private one.
  • Channel message responses (MessageResponse) carry author_removed. It is true when the author opted out of community features or deleted their account; the message's author_id is then the nil UUID. A message relayed from Discord by someone with no linked account carries false.

Changed

  • GET /feeds/personal, GET /feeds/social, GET /feeds/shard/{shard_id} and GET /feeds/community cut their pages in the store. A cursor now reaches every item of a feed, where it used to stop after the first 1,000, and every page but the last holds limit items, however many items of opted-out users sort between them. next_cursor is still the last item's id.
  • PATCH /channels/{channel_id}/messages/{message_id} is refused with 403 Forbidden and EDIT_DENIED when the author was removed, or the message deleted, after the route read it. The edit no longer writes over the removal. EDIT_DENIED's message is now "This message cannot be edited", which names none of its causes.
  • Deleting an account also deletes its share pages.
  • The OpenAPI declarations of PATCH /account/me/privacy and POST /account/me/consents/withdraw list the 415 and 422 answers for a body the API cannot read, and the PATCH's 500 and 503.

Removed

  • ConsentType no longer has Community. A POST /account/me/consents/withdraw whose consent_type is "Community" is refused with 422 Unprocessable Entity and changes nothing.

Internal

  • Migration 031 adds users.community_opt_out and removes Community from the consent_records consent-type CHECK. It deletes no consent record: if one of that type exists, the migration stops and changes nothing.
  • Migration 032 adds channel_messages.author_removed, false by default.
  • Migration 033 adds users.community_opt_out_cleanup_pending, false by default.

Removed

  • Relationship responses (RelationshipView, returned by the Echo relationship endpoints) no longer carry cross_user_consent. It was always false: nothing ever set it, and nothing read it.
  • ConsentType no longer has CrossUserInteraction. No endpoint ever granted it. A POST /account/me/consents/withdraw whose consent_type is "CrossUserInteraction" is now refused with 422 Unprocessable Entity, as for any other unknown consent type, and changes nothing. Withdrawing Community still turns on solo_mode.

Internal

  • Migration 030 drops echo_relationships.cross_user_consent and removes CrossUserInteraction from the consent_records consent-type CHECK. It deletes no consent record: if one of that type exists, the migration stops and changes nothing.

v0.20.0 — R155 — TypeScript SDK labelled as being rebuilt (September 2026)

Changed

  • Developer portal: the TypeScript SDK is labelled as being rebuilt across 5 files (sdk.md, websocket-events.md, guides/index.md, guides/getting-started-api.md, guides/building-custom-client.md), and in the SDK's own README.md. The SDK does not match the current API, and the pages now say not to use it and to call the API directly. It is being rebuilt, generated from the server's own code.
  • Developer portal: sdk.md no longer says the SDK's types.ts is Specta-generated or that it produces the same shapes as generated.ts; neither was true. guides/building-custom-client.md no longer tells readers to vendor types.ts.
  • Developer portal: sdk.md, getting-started.md, guides/getting-started-api.md and guides/building-custom-client.md no longer say JWT is the only authentication path. The API accepts Authorization: ApiKey <token> as well as Authorization: Bearer <jwt>.
  • Developer portal: api-reference.md states both authentication schemes above the rendered reference, when an API key needs a scope, and that admin accounts and WebSocket routes take only a JWT. The reference's per-endpoint security labels still list only the JWT, and are being corrected.
  • Developer portal: sdk.md gives no way to obtain the SDK. Its Installation section, with the git clone and cp -r steps, is removed, and the page states that calling the API directly is the way to use the API today. The SDK's README.md loses its Installation section and its npm install line.

v0.19.0 — Lane I 1d (May 2026)

Changed

  • ME-DXG-001 (Developer Experience Guide) v1.3 → v1.4: §1 Enforceable-from clarified to Lane I Commit 1; §3/§3.6/§4.4/§6.1/§7.1 canonical-domain placeholders eliminated (zero hostname placeholders remain anywhere in the document); §5 Contributing now references the Lane I 1c published CLA Gist + cla-assistant.io bot + ME-ILF-001 v1.1 governing-law cross-ref; §6 SDK section rewritten to "TypeScript SDK (In Development)" reflecting the unpublished state of in-repo packages/sdk (@echolabs/multiverse-echoes per packages/sdk/package.json).
  • Developer portal: SDK references aligned across 5 files (sdk.md, getting-started.md, guides/getting-started-api.md, guides/building-custom-client.md, websocket-events.md) — npm-install lines replaced with direct fetch/WebSocket examples until first npm publish.
  • ME-PLB-001 (Development Playbook) v2.1 → v2.2: §19.1 folder tree no longer references the deleted stub mkdocs.yml. The live MkDocs config is mkdocs-developer.yml.

Internal

  • Repo-root mkdocs.yml stub deleted (4-line file with no nav). mkdocs-developer.yml is the sole MkDocs config; .github/workflows/deploy-developer-portal.yml references it explicitly.
  • CLAUDE.md folder-tree mention of mkdocs.yml removed (Rule #14 sibling-fix).

v0.18.0 — Lane I 1c — CLA suite publish + cla.yml Gist wiring (May 2026, squash-merge 28d2155)

Added

  • Echo Labs Contributor License Agreement published as a public Gist at gist.github.com/asiamtm/3f9566d5c60080dfe23e5d6c39984034. ICLA (individual) is the default; CCLA (corporate) is available on request via [email protected]. Both governed by Singapore law.
  • cla.yml workflow on the open-source client repo wired to the published Gist via cla-assistant.io. First-time contributors are prompted to sign before their PR can be merged.
  • ME-ILF-001 (IP & Legal Framework) v1.0 → v1.1: §5.2 amendment + §1.0 Founder-accepted-risk carve-out for the CLA suite. Filenames bumped to EchoLabsME-{ICLA,CCLA}-v1.0.1.md post-amend (Rule #14 sibling sweep covering §8 comma + line 101 statute-name fixes parallelling CCLA DOC-3).

v0.17.0 — Lane I 1b — Mirror-sync workflow (May 2026, paperwork 63bbbb7)

Added

  • Mirror-sync workflow propagating the in-engine client/ directory to the public OSS mirror at github.com/echolabs-me/multiverse-echoes-client. On every push to main that touches client/**, the public mirror receives a clean replay so external contributors always see the current shipping client.
  • client/.github/workflows/cla.yml mirrored to public root .github/workflows/cla.yml via the same propagation path.

v0.16.0 — Lane I 1a — Public OSS foundation (April 2026, squash-merge a7d0464)

Added

  • Open-source client foundation under MIT License at github.com/echolabs-me/multiverse-echoes-client. Initial publish: 122-file Prettier pass (prettier-plugin-tailwindcss removed from .prettierrc to resolve the dual class-sorter conflict with eslint-plugin-better-tailwindcss); .prettierignore for src/types/generated.ts; community surface — CODEOWNERS, dependabot.yml, SECURITY.md, .env.example, .gitignore.
  • scripts/pre-commit augmented with npm run format:check step.

v0.15.0 — Lane MOCK_SHARD-cleanup (April 2026, squash-merge 46f53dd)

Internal

  • E2E + a11y test infrastructure — MOCK_SHARD canonical wire shape parity with the server contract. Drives the e2e accessibility suite to zero failures across mock-API render coverage.

v0.14.0 — Lane Install-Discipline (April 2026, squash-merge 8f81983)

Internal

  • npm ci-only install discipline codified for all Node projects in the repo (currently client/package.json). pnpm, yarn, and bun are forbidden as installers — package-lock.json is the only committed lockfile and those tools do not read it (silently bumping transitive dependencies past their pins). .github/workflows/install-discipline.yml rejects any PR or main-push that introduces pnpm-lock.yaml, yarn.lock, or bun.lockb.

v0.13.0 — Lane SEO-LangPage (April 2026, squash-merge 69e2dba)

Added

  • Canonical, hreflang, and Open Graph tags on the / (root) public page across all 21 supported locales. Improves search-engine canonicalization and social-preview surfaces.

v0.12.0 — Lane E — Public Pages + Developer Portal launch (April 2026, squash-merges 6e61337 + 97db344)

Added

  • GET /api/v1/users/{user_id} and GET /api/v1/public/users/{user_id} — read public profile by user ID; the public variant is unauthenticated and visibility-gated (returns 404 for Private profiles, suspended accounts, accounts pending deletion, deleted accounts; uniform 404 surface so visibility state cannot be inferred).
  • GET /api/v1/users/{user_id}/echoes — list a user's public Echoes.
  • GET /api/v1/marketplace/items and GET /api/v1/marketplace/items/{item_id} — browse marketplace catalog.
  • POST /api/v1/marketplace/purchase — purchase a marketplace item (request body: { item_id }).
  • og-router Cloudflare Worker extended with /users/{user_id} crawler dispatch path consuming the new public-user endpoint; existing /share/{token} Lane H surface preserved.

Public Surface

  • MkDocs Developer Portal live at developers.echolabsme.com. Auto-deployed via .github/workflows/deploy-developer-portal.yml on push to main affecting docs/developer-portal/**, mkdocs-developer.yml, crates/api/src/routes/**, crates/api/src/openapi_schemas.rs, or crates/api/src/bin/generate-openapi.rs. The API Reference page renders a build-time openapi.json snapshot via self-hosted Redoc.
  • New public pages on the main client at /users/{user_id} (UserProfilePage), /marketplace (MarketplacePage), and the About page.

v0.11.0 — Lane C — Subscription Health (April 2026, squash-merges bd15ac1 + 80eda52)

Added

  • GET /api/v1/me/billing-health — read your current dunning state (5-phase state machine: Active, GraceHardWarning, Suspended, Cancelled, Recovered) and account-level billing snapshot.
  • GET /api/v1/admin/billing/dunning-states — admin-only list of users in non-Active dunning phases.
  • GET /api/v1/admin/billing/revenue-snapshots — admin-only daily revenue-snapshot history.
  • POST /api/v1/admin/billing/dunning-states/{user_id}/{provider}/override — admin override of dunning phase (override_reason 1..=500 chars, audit-logged).
  • POST /api/v1/admin/billing/revenue-snapshots/trigger — admin synchronous run of the revenue-snapshot pass.
  • GET /api/v1/admin/billing/notification-dead-letters — admin-only list of failed billing-notification deliveries.
  • POST /api/v1/admin/billing/notification-dead-letters/{user_id}/{kind}/{period_anchor_unix}/redrive — admin retry of a dead-lettered notification.
  • GET /api/v1/admin/billing/dunning-pass-reports — admin-only ring-buffer of recent dunning-pass observability reports.
  • 3 new WorldEventPayload variants exposed via dashboard WebSocket: PaymentFailed, DunningPhaseChanged, RevenueSnapshotGenerated.
  • BillingBanner client component with WS-driven live updates (5 banner variants — GracePeriod, HardWarning, Suspended, RefundReceived, Recovered).
  • Cross-provider in-app subscription-health notifier with Stripe charge.refunded handler. 5 new EmailTemplate::BILLING_* constants with 210 Tera template files (10 EN + 200 non-English across 21 supported locales).

Changed

  • ME-MIS-001 (Monetisation & Influence System) v1.5 → v1.10 across the lane (new §5.4 dunning state machine, §5.4.5 banner copy, §5.4.6 admin override, §5.4.7 cross-provider notifier, §5.4.7.3 dead-letter ledger).

v0.10.0 — Lane H — Share Subsystem (April 2026, squash-merge ca6741f)

Added

  • POST /api/v1/feeds/{item_id}/share and GET /api/v1/feeds/share/{token} — generate and resolve share tokens for personal-feed items. Token TTL defaults to 30 days; expired tokens 404 cleanly. Snapshot stored in Cloudflare KV (SHARE_SNAPSHOTS_KV binding).
  • og-router Cloudflare Worker /share/{token} branch — unconditional dispatch (humans + crawlers both hit the worker; share landing has no SPA route equivalent). Fetches the snapshot from KV and emits OG tags + canonical disclaimer + og:image:width/height fallback for missing avatars; serves a uniform 404 for missing/expired tokens.
  • Viral-content monitoring admin endpoint (GET /api/v1/admin/share/viral-content).
  • Share-subsystem analytics events surfaced to the client (Lane H Commit 5).

v0.9.0 — Domain cleanup v2 (April 2026, 17d008a)

Changed

  • All references to echolabs.me migrated to canonical echolabsme.com. API base now https://api.echolabsme.com; static client served from https://echolabsme.com via Cloudflare Pages; WebSocket served from wss://api.echolabsme.com/ws/.... cors_allowed_origins updated to include both https://echolabsme.com and https://www.echolabsme.com.

v0.8.0 — Phase 6B/C/D — UX atmosphere + security audit + retention + deletion pipeline (March 2026)

Added

  • Account deletion pipeline with grace-period revocation + cascade across all user-owned tables (Echo, DiaryEntry, Relationship, MemoryNode, content_translations, user_input_originals).
  • Retention enforcer (100% coverage across user-owned tables) and Redb migrations for payment, deletion ledger, breach record models.
  • FLUX 1280×720 image generation with batch endpoint, image-prompt diversity, Community Pulse length filter.

Changed

  • Phase 6D security audit close-out (Sessions 085-088). Live B200 deploy verification pinned the audited surface in production.
  • GPU shutdown helper (scripts/start.sh::stop_gpu_service) — SIGTERM + 60s grace + SIGKILL only after grace, preventing orphaned VRAM allocations on PyTorch sidecars.

Internal

  • ruff + mypy pre-commit + CI for services/ Python sidecars; Python 3.12 modernization across the repo.

v0.7.0 — Phase 6A — Billing infrastructure (March 2026)

Added

  • NOWPayments (cryptocurrency) and Xaman (XRP wallet sign-in) payment provider implementations against the PaymentProvider trait.
  • Stripe payment integration (Phase 6A close).
  • Billing surface integrated into the API: subscription tier enforcement, webhook handlers, payment record persistence.

v0.6.0 — Phase 5 — Visual polish + video + PDF export + initial SDK + Developer Portal expansion (February 2026)

Added

  • Video pipeline (talking-Echo highlights via MuseTalk + LivePortrait + voice classifier).
  • PDF Story Export — generate per-Echo or per-shard PDFs from accumulated diary entries + life events.
  • Initial TypeScript SDK at packages/sdk/ (in-development; not yet published to npm — see SDK page).
  • Developer Portal expansion (Phase 5C Step 14) — first iteration of the MkDocs portal that became developers.echolabsme.com in v0.12.0.

Changed

  • Visual polish pass + animation/transition refinement + reduced-motion audit + canvas 2D color extraction.

v0.5.0 — Phase 4 (March 2026)

Added

  • Direct Echo Conversation endpoints (/echoes/{id}/conversations, /conversations/{id}/messages)
  • Oracle assistant endpoint (/oracle/ask)
  • Search endpoints (/search/echoes, /search/diary, /search/events, /search/shards, /search/messages)
  • Story export endpoints (/account/me/export)
  • API key management (/keys)
  • Account deletion with grace period
  • Notification preferences endpoints
  • Admin dashboard endpoints (admin-only)
  • OpenAPI spec at /api/docs and Swagger UI at /api/docs/ui

v0.4.0 — Phase 3 (March 2026)

Added

  • Shard management endpoints (public/private creation, access control, travel)
  • Channel system with 5 channel types
  • Channel messages with auto-moderation
  • Social graph endpoints (follow, block, mute)
  • User reporting system
  • Community moderation tools
  • Discord bridge
  • Global events (admin)
  • WebSocket streams for Shards and Channels

v0.3.0 — Phase 2 (February 2026)

Added

  • Multi-Echo tick processing
  • Echo relationships and encounter system
  • Hibernation state machine
  • Multi-Echo tier enforcement

v0.2.0 — Phase 1 (January 2026)

Added

  • Echo CRUD endpoints
  • WebSocket Echo event streaming
  • Personal feed and notifications
  • Influence point system
  • User account management (password reset, data export, sessions)
  • System tick endpoint (admin)