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-noticerecords 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 409ACCOUNT_FENCEDwhen the account is being deleted.shared_shard_notice_acknowledged_atis a new field onGETandPATCH /account/me/privacy: the acknowledgment's time in RFC 3339, ornulluntil the user acknowledges the notice.
Changed¶
- 409
SHARED_SHARD_NOTICE_REQUIREDfromPOST /echoeswhenshard_idnames a Public shard, and fromPOST /echoes/{id}/travelwhen 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 /echoesnow reads and checks the shardshard_idnames before it translates the request's text, so an unknown shard (404) or one that is not Public (400SHARD_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_nameis a new field on each messageGET /channels/{channel_id}/messagesreturns. It holds the Discord name of the author of such a relay, and isnullfor every other message.- When it is set,
author_display_nameis that name. It used to be"Unknown". - Such a relay's
contentis the text alone. It used to beDiscord | <name>: <text>. Relays stored before this change keep their old content and have noexternal_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_UNAVAILABLEwhen the new file cannot be written. The existing avatar and itsavatar_urlare unchanged. It used to be lost. - When the new
avatar_urlcannot be stored, the route answers that failure, deletes the new file, and leaves the existing avatar andavatar_urlunchanged. 409ACCOUNT_FENCEDwhen the account is being deleted, and 404NOT_FOUNDwhen 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_JSONwhen the body is not JSON. - 415
UNSUPPORTED_MEDIA_TYPEwhen the request has no JSON content type. - 422
VALIDATION_INVALID_BODYwhen the body is JSON of the wrong shape. - 413
PAYLOAD_TOO_LARGEwhen the body is over the route's limit. - 400
UNREADABLE_BODYwhen 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_TYPEfor a file whose bytes are not a PNG, JPEG or WebP image. It used to answer 400INVALID_TYPE. - 400
INVALID_MULTIPARTfor 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 400NO_FILE, and one that failed while the file was read answered 400UPLOAD_FAILED. - 413
PAYLOAD_TOO_LARGEfor 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 400UPLOAD_FAILED, so a portrait between 1 MB and 2 MB could not be uploaded, and its 400FILE_TOO_LARGEfor 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 asimage/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_TYPEfor a file that is not an image. It used to answer 400INVALID_TYPE. Nothing is sent to Discord. - 400
INVALID_MULTIPARTfor 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 400NO_FILE, and one that failed while the file was read answered 400UPLOAD_FAILED. - 413
PAYLOAD_TOO_LARGEfor 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 400UPLOAD_FAILED, so its 400FILE_TOO_LARGEfor an image over 5 MB was never sent. - 502
IMAGE_UPLOAD_FAILEDwhen 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_eventfeed item'sitem_idis 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/communityandGET /feeds/{item_id}. - For a user whose locale is not English, a
life_eventfeed item now comes back with its translatedtitleandbodyand withcontent_localeset to that locale, once the translation is stored. It used to come back in English withcontent_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,significanceand 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 significance500. 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_typeis one of six values:echo,shard,diary,event,messageandfeed.GET /search/echoes,/search/shards,/search/diary,/search/eventsand/search/messagessend the first five, as before.GET /search/feed,GET /search?type=feed, and the feed results the globalGET /searchmerges, now senditem_type: "feed". They used to send the feed item's kind in a second spelling, such asDiaryEntryorLifeEvent.item_typenames the kind of search that found the result. Anitem_idalone does not tell the kinds apart:/search/diaryand/search/eventssend feed item ids too.
Added¶
SearchResult.feed_item_type: on afeedresult, 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 isnull.
v0.22.0 — R177 — the community opt-out, and the Community consent type retired (September 2026)¶
Added¶
PATCH /account/me/privacyacceptscommunity_opt_out, andGET /account/me/privacyand the PATCH's response return it. It isfalseby default: community features rest on legitimate interest, and no consent is asked for them.- While
community_opt_outis 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-voteandPOST /feeds/{item_id}/shareare refused with403 Forbiddenand the new codeCOMMUNITY_OPTED_OUT, before anything is written; POST /social/follow/{user_id}naming the user is refused with403 ForbiddenandCOMMUNITY_OPTED_OUT;- the user's messages from a linked Discord account are not relayed;
- other users'
GET /feeds/social,GET /feeds/shard/{shard_id}andGET /feeds/communityleave out the user's items, andGET /feeds/{item_id}answers404 Not Foundto them for such an item. The user still sees their own items, andGET /feeds/personalis unchanged. - Turning
community_opt_outon 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 Unavailablewith the new codeCOMMUNITY_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/privacyand the PATCH's response carrycommunity_opt_out_cleanup_pending. It istruefrom the momentcommunity_opt_outis turned on until the removal of the name completes, so it staystrueafter a503or a500. Turningcommunity_opt_outoff sets itfalseand 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 answers200 OKwith the settings as they then stand, so itscommunity_opt_outcan befalse.- 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) carryauthor_removed. It istruewhen the author opted out of community features or deleted their account; the message'sauthor_idis then the nil UUID. A message relayed from Discord by someone with no linked account carriesfalse.
Changed¶
GET /feeds/personal,GET /feeds/social,GET /feeds/shard/{shard_id}andGET /feeds/communitycut 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 holdslimititems, however many items of opted-out users sort between them.next_cursoris still the last item's id.PATCH /channels/{channel_id}/messages/{message_id}is refused with403 ForbiddenandEDIT_DENIEDwhen 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/privacyandPOST /account/me/consents/withdrawlist the415and422answers for a body the API cannot read, and the PATCH's500and503.
Removed¶
ConsentTypeno longer hasCommunity. APOST /account/me/consents/withdrawwhoseconsent_typeis"Community"is refused with422 Unprocessable Entityand changes nothing.
Internal¶
- Migration
031addsusers.community_opt_outand removesCommunityfrom theconsent_recordsconsent-type CHECK. It deletes no consent record: if one of that type exists, the migration stops and changes nothing. - Migration
032addschannel_messages.author_removed,falseby default. - Migration
033addsusers.community_opt_out_cleanup_pending,falseby default.
v0.21.0 — R206 — cross_user_consent and the CrossUserInteraction consent type removed (September 2026)¶
Removed¶
- Relationship responses (
RelationshipView, returned by the Echo relationship endpoints) no longer carrycross_user_consent. It was alwaysfalse: nothing ever set it, and nothing read it. ConsentTypeno longer hasCrossUserInteraction. No endpoint ever granted it. APOST /account/me/consents/withdrawwhoseconsent_typeis"CrossUserInteraction"is now refused with422 Unprocessable Entity, as for any other unknown consent type, and changes nothing. WithdrawingCommunitystill turns onsolo_mode.
Internal¶
- Migration
030dropsecho_relationships.cross_user_consentand removesCrossUserInteractionfrom theconsent_recordsconsent-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 ownREADME.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.mdno longer says the SDK'stypes.tsis Specta-generated or that it produces the same shapes asgenerated.ts; neither was true.guides/building-custom-client.mdno longer tells readers to vendortypes.ts. - Developer portal:
sdk.md,getting-started.md,guides/getting-started-api.mdandguides/building-custom-client.mdno longer say JWT is the only authentication path. The API acceptsAuthorization: ApiKey <token>as well asAuthorization: Bearer <jwt>. - Developer portal:
api-reference.mdstates 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.mdgives no way to obtain the SDK. Its Installation section, with thegit cloneandcp -rsteps, is removed, and the page states that calling the API directly is the way to use the API today. The SDK'sREADME.mdloses its Installation section and itsnpm installline.
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-echoesperpackages/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 directfetch/WebSocketexamples 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 ismkdocs-developer.yml.
Internal¶
- Repo-root
mkdocs.ymlstub deleted (4-line file with no nav).mkdocs-developer.ymlis the sole MkDocs config;.github/workflows/deploy-developer-portal.ymlreferences it explicitly. - CLAUDE.md folder-tree mention of
mkdocs.ymlremoved (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.ymlworkflow 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.mdpost-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 tomainthat touchesclient/**, the public mirror receives a clean replay so external contributors always see the current shipping client. client/.github/workflows/cla.ymlmirrored to public root.github/workflows/cla.ymlvia 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-tailwindcssremoved from.prettierrcto resolve the dual class-sorter conflict witheslint-plugin-better-tailwindcss);.prettierignoreforsrc/types/generated.ts; community surface —CODEOWNERS,dependabot.yml,SECURITY.md,.env.example,.gitignore. scripts/pre-commitaugmented withnpm run format:checkstep.
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 (currentlyclient/package.json).pnpm,yarn, andbunare forbidden as installers —package-lock.jsonis the only committed lockfile and those tools do not read it (silently bumping transitive dependencies past their pins)..github/workflows/install-discipline.ymlrejects any PR ormain-push that introducespnpm-lock.yaml,yarn.lock, orbun.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}andGET /api/v1/public/users/{user_id}— read public profile by user ID; the public variant is unauthenticated and visibility-gated (returns 404 forPrivateprofiles, 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/itemsandGET /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.ymlon push tomainaffectingdocs/developer-portal/**,mkdocs-developer.yml,crates/api/src/routes/**,crates/api/src/openapi_schemas.rs, orcrates/api/src/bin/generate-openapi.rs. The API Reference page renders a build-timeopenapi.jsonsnapshot 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_reason1..=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
WorldEventPayloadvariants 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.refundedhandler. 5 newEmailTemplate::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}/shareandGET /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_KVbinding).- 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/heightfallback 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.memigrated to canonicalecholabsme.com. API base nowhttps://api.echolabsme.com; static client served fromhttps://echolabsme.comvia Cloudflare Pages; WebSocket served fromwss://api.echolabsme.com/ws/....cors_allowed_originsupdated to include bothhttps://echolabsme.comandhttps://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
PaymentProvidertrait. - 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.comin 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/docsand 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)