Gemmein: what changed, every release (newest first) If anything you remember differs from this file, this file is right. As JSON: https://docs.gemmein.com/changes.json · In a terminal: npx gemmein changes [--since ] [--json] The full guide: https://docs.gemmein.com/llms-full.txt ------------------------------------------------------------------------ Gemmein 0.23.0 (2026-10-13) Packages: gemmein 0.23.0 · @gemmein/sdk 0.18.0 · @gemmein/mcp 0.16.0 After one browser approval the AI drives the build: sign-in by approval, declared collections, secrets saved for the AI, a Stripe key of 5 permissions plus 2 only if you sell plans (Gemmein sets up the billing portal), members on several devices, and a test kit so the AI proves the app in Development itself. Changed: - Pricing Was: Live apps paid one plan whose price moved in five pre-agreed steps from $50 up to $150 a month, set by the previous month's active people across the account (up to 10,000 people). Now: Each Live app pays a flat price for the tier its founder picks: Launch $10 a month (up to 1,000 active people, 5 GB of storage), Live $40 (5,000 people, 50 GB) or Growing $100 (15,000 people, 150 GB); past 15,000 people, talk to Gemmein. Annual billing is 2 months off (save 17%): $100, $400 or $1,000 a year. Active people are a limit, never a price: past a tier's people the app keeps serving for the lesser of +10% or 7 days and the owner is emailed; then new people are refused at sign-in (403 people_limit_reached, "This app isn't taking new sign-ups right now.") until the app moves up a tier, while everyone already in keeps working. Nothing is ever charged automatically and no tier changes on its own. Building is free; downloads are fair use, not metered. New error codes: people_limit_reached, invalid_plan, interval_locked, not_live - Storage Was: On Live, storage grew with the app's active people (50 MB a person over a 100 GB base), plus any $5 blocks of 100 GB; uploads were refused past twice the ceiling. Now: Storage is flat per tier (5 GB, 50 GB or 150 GB, never per person), plus 50 GB for each storage pack the founder adds ($5 a month, or $50 a year on annual billing). Past it the same grace as people applies — the lesser of +10% or 7 days, and an email — then new uploads are refused until files are deleted, a pack is added or the app moves up a tier. Files already stored stay. Development is capped at 1 GB, uploads refused past twice that. - Signing in the CLI Was: The CLI linked to the cloud app with a CLI key your human copied from the dashboard's Setup page and pasted. Now: `npx gemmein login` prints a link and a short code; your human checks the code and presses Approve. No key is shown, copied or pasted. A Development approval lasts 24 hours. New error codes: login_required, slow_down, pick_an_app, wrong_app, unknown_code, unknown_request, invalid_machine, invalid_scope, invalid_target, invalid_action, not_pending, not_active, rate_limited, too_many_pending, approval_limit, parent_ended, approval_key, local_rail - Live actions Was: Going live, promoting and `sync --live` asked for a typed confirmation word, and `sync --live` needed a one-hour sync key pasted from the dashboard. Now: Every Live action asks for its own browser approval, once, within 10 minutes, for exactly the plan shown item by item. There is no word to type and no key to paste. Approving any Live action also asks your human for the code Gemmein emails (once every 10 minutes) — except going live, where paying for the plan is the confirmation. New error codes: live_approval_required, live_approval_used, live_approval_expired, live_approval_wrong_action, approval_plan_changed, too_many_changes, plan_required, invalid_relay - Collections Was: A collection's fields were picked up from the records written in Development and fixed when the app went live. Now: Every community, shared and direct collection declares its fields in gemmein/collections/.json. Writes to one with no fields are refused, a field not declared is refused, and go-live refuses until every one is declared. Nothing is guessed from test data. New error codes: collection_not_declared, undeclared_field, undeclared_collections - Collections Was: A collection's fields locked when the app went live; promote could only add new ones. Now: A live collection only grows: a field may be loosened freely; tightening one shows how many records it would catch and asks, and only your human's yes applies it, to new writes and edits only (without it the promote is refused, tighten_needs_decision); a field is retired with "deprecated": true, never deleted; a field never changes kind; older records can show a declared "default". New error codes: field_deprecated, tighten_needs_decision, field_type_change, field_missing_from_declaration - Going live Was: Going live on the dashboard showed a confirm pop-up after payment, and a collection with no fields yet stayed in Development. Now: Going live is one screen at a time: pay, then progress, then a Live welcome card that keeps the result (what went live, anything that needs your human). Every declared collection goes live with exactly its declared fields, empty or not. - Going live Was: When a founder's Gemmein subscription ended or a payment failed, the whole account was suspended: every app refused every request with account_suspended, Development included. Now: A failed Gemmein payment starts a 7-day grace: Live keeps working, the founder is emailed on days 0, 3, 5 and 6 and the dashboard counts down; on day 7, still unpaid, or at once when the plan ends, only Live pauses: app keys get 503 live_paused with "This app is temporarily unavailable. Please try again later.", server keys the founder's sentence; webhooks to Live are answered 200 and held, then run in order when Live is back (dropped after 30 days, logged, the founder told); Development keeps working, free, within its caps; nothing is deleted. Go-live and promote are refused while paused. A plan cancelled at period end is warned 7 days and 1 day before; paying again lifts the pause at once. New error codes: live_paused - Secrets Was: Secrets Gemmein made for relays and pipeline tools were shown once in the sync output for your human to copy to the app's host. Now: Every secret Gemmein makes goes to gemmein/.data/secrets.json (kept out of git) with its variable name and who uses it; sync prints one line. You set them on the host yourself. A relay with no call_url gets no signing secret. New error codes: no_call_url - Sync Was: `npx gemmein sync` stopped at the cloud's change limit and had to be run again, and printed a long log. Now: Sync finishes in one run: it waits out the limit (30 changes a minute), syncs payments before collections, sends product files by hash and skips unchanged ones, and ends with a short summary and "What you need to do: nothing." or the one thing. `--verbose` prints every line; `--prune` removes Development relays whose file is gone. - Allowed addresses Was: A browser call worked only from a listed domain or localhost; after go-live, from verified domains only, for every key. A builder's preview address (random and changing) was refused until it was listed. Now: The Development app key works from any secure (https) address (a browser builder's preview such as Lovable, Bolt, v0 or Replit, a host's preview deploy, your own domain) and from localhost, before and after go-live, with nothing to list. The Live app key works only on verified domains, and `npx gemmein dev` keeps localhost only. A page on a Development key that isn't https is refused with reason `insecure`, and every refusal carries `environment`. Development sends 100 sign-in code emails a day per app (test members' codes don't count). Inside a frame, `checkout()`, `buy()` and `manage()` open Stripe in a new tab. - Payments Was: The Stripe restricted key held five permissions, and an app selling plans also set up Stripe's customer portal by hand. Now: The key holds 5 permissions, plus 2 only if you sell plans (Customer portal and Subscriptions, Write); none can pay out, refund, or read cards. Gemmein sets up the customer portal from your plans. A first plan on a five-permission key is refused before anything is made. New error codes: stripe_key_plan_rows, no_plans, portal_unavailable, portal_rate_limited, payments_unreadable - Payments Was: Deleting a member's account left their Stripe subscriptions as they were. Now: Deleting a member's account cancels every Stripe subscription they bought at the end of its period (never charged again, nothing refunded); one still owing is cancelled at once. If Gemmein can't read the app's payments at that moment, nothing is deleted and the call answers 503 erase_unavailable; try again. New error codes: erase_unavailable - Members Was: A person signing in on a second device ended their session on the first. Now: Members stay signed in on up to 10 devices; an 11th sign-in signs out the oldest. g.auth.devices() lists them; signOutDevice(id) and signOutEverywhere() sign out. Signing out another device needs a recent sign-in, or 403 confirm_required and an emailed code. New error codes: device_not_found, invalid_device - Members Was: g.auth.logout() revoked the person's one server session. Now: g.auth.logout() ends this device's session only; the person's other devices stay signed in (signOutEverywhere() ends them all). - Members Was: A server test session (testSession) signed the member out of their real browser. Now: testSession signs in alongside: test sessions keep their own ten and never end a real one. - Members Was: A suspended person was sent a sign-in code and refused after typing it. Now: A suspended person is sent no code; the start answers the same 202 every address gets. - IP addresses Was: Logs rows kept the request's IP address, ordinary writes included: 90 days in Logs, then a 12-month archive with the address still in it; the founder saw it only on a person's activity. Now: Gemmein records the full IP address only on security events, for members and the founder's own account: a sign-in code asked for or entered (allowed or refused), a session started, a sign-out (one device or everywhere), a rate-limited or refused sign-in attempt, a suspended person's attempt, a confirm code, a data export or deletion, a checkout a member starts, a browser approval asked for from the CLI, a secret key made or revoked. Ordinary reads and writes carry no address. It is read from the connection, never from a header the app sends. - Dashboard sign-in Was: When a founder signed in somewhere else, the other dashboard session ended with only "Your session ended". Now: A founder has one dashboard session at a time: signing in somewhere else ends the other, and its sign-in page says "You were signed out because you signed in somewhere else." - Collection features Was: "publishing": "owner approves" worked on community collections only. Now: "owner approves" also works on shared collections (members-only moderated feeds). Waiting posts are approved or sent back inline on the Collections page. New error codes: not_moderated, not_waiting, posts_unpublished - Email and domains Was: A domain that sends email needed five DNS records. Now: It needs six: DMARC (v=DMARC1; p=none at _dmarc.) joins the others. If the main domain already publishes DMARC, that covers it and nothing is added. - Email and domains Was: The sign-in email and Gemmein's plain design (purchase, plan-start, notify) were always drawn light, in dark mode too. Now: With no brandColour, backgroundColour or logoUrl in gemmein/emails/sign-in.json and no email accent in the console, the sign-in email and the plain design follow the reader's light or dark mode. With any of those set, they stay light, the background the colour or logo was made for. - Relays Was: Schedules ran every 15m, 30m, 1h, 6h, 12h or 1d. Now: Schedules also run weekly: { "every": "1w", "on": "monday", "at": "09:00" }. - Relays Was: An hmac_sha256_header receiver checked an HMAC-SHA256 of the body, or of . with the timestamp in a header of its own. Senders that sign another way could not be verified: a timestamp inside the signature header (Paddle Billing, Slack), HMAC-SHA1 (Intercom), the URL with the body (Square), the URL with form fields (Twilio), a signature inside the body (Mailgun), or a public-key signature (SendGrid's ECDSA, Discord's Ed25519). Now: The hmac_sha256_header scheme is now `signature`: a signature recipe, described from the sender's own docs. A definition naming hmac_sha256_header is refused with 400 invalid_definition, naming the new scheme. algorithm is hmac-sha256 (the default), hmac-sha1, hmac-sha512, ed25519 or ecdsa-p256-sha256. The signature is in a header (signatureKey and separator for its parts) or in a body field (signatureField). The timestamp is in timestampHeader, timestampKey or timestampField. signedPayload is the signed text, built from {timestamp}, {url} and one of {body}, {params} or {token}. encoding is hex, base64 or base64url. Paddle Billing, Slack, GitHub, Intercom, Square, Twilio, Mailgun, SendGrid and Discord verify. Definitions without the new fields verify as before. - Relays Was: A signature timestamp was read as a number, so a sign, a decimal, an exponent, hex or a padded spelling could still verify. Now: A timestamp verifies only as 1 to 13 digits; anything else never verifies. - Relays Was: The dashboard's shared-token receiver template said GoCardless, Lemon Squeezy and Paddle send a shared token. Now: They sign an HMAC: GoCardless and Lemon Squeezy a plain hex HMAC of the body (header alone), Paddle Billing with timestampKey, signatureKey, separator and signedPayload. - AI tools Was: A transcription tool accepted any response_format when saved. Now: An unsupported response_format is refused when the tool is saved, in gemmein dev and at sync alike, naming the formats that work. New error codes: unsupported_response_format - AI tools Was: A chat tool ran only on the three hosts (OpenAI, Anthropic and Google); any other model needed your own pipeline. Now: A chat tool also runs on any endpoint that speaks the OpenAI Chat Completions format — provider `openai-compatible`: your human adds a name, the base URL, the key and (optionally) the models on the AI page (OpenRouter, Mistral, Groq, DeepSeek, xAI, Together AI, Fireworks AI; Ollama under `gemmein dev` through GEMMEIN_AI_URL_OPENAI_COMPATIBLE). Model ids are the endpoint's own (slashes allowed). The base URL is checked on every call — https only, public addresses only, redirects never followed — and a new base URL is saved only with its key pasted again. Text only unless the tool ticks image. New error codes: invalid_base_url, endpoint_refused - MCP server Was: search_docs returned passages of the packaged guide and reference by line; reference and guide returned whole documents. Now: search_docs answers first with the docs site's sections that match, each a direct markdown link with its anchor (https://docs.gemmein.com/.md#
). guide and reference take an optional topic and return only the matching sections; guide with no topic ends with every page's markdown link. explain_error names the code's page, https://docs.gemmein.com/errors/. Offline: the section index ships with the server. Removed: - Pricing Was: Holding the bill and the ceilings at a price step from the dashboard. Now: There is no band to hold: the price is the tier the founder picks on Usage & billing, and the tier's people are where the ceilings stop. - Signing in the CLI Was: A CLI key shown on the Setup page and rotated on Secret keys. Now: There is no CLI key. The CLI holds its own key in gemmein/.data/cloud.json after the browser approval; Revoke on the Approvals page ends it. Codes no longer answered: cli_key_unavailable - Going live Was: A suspended account answered every request, on every app, with account_suspended. Now: No request is answered account_suspended. A paused Live answers 503 live_paused; Development keeps working. Codes no longer answered: account_suspended - Payments Was: Pasting a customer portal link on the Payments page. Now: Gemmein makes the portal; `g.subscriptions.manage()` opens it for the member. There is nothing to paste. Codes no longer answered: portal_not_set_up, invalid_portal_link, payments_not_set_up Added: - Live actions: The dashboard's Approvals page lists the machines working now (Revoke ends one at once), the requests waiting (Cancel) and the history of what each approval was used for. - Collections: The collection file holds every setting (rule, means, unlockedBy, publishing, openFields, directInitiation, fields). A key it doesn't know is refused by name, and sync applies the file's settings to Development. New error codes: unknown_collection_setting, invalid_fields, invalid_direct_initiation - Secrets: A provider's own signing secret (Stripe's whsec_…, or one an HMAC sender gives you) has a home: pipe it to `npx gemmein relays secret ` (Development) or paste it on the relay's page in the dashboard. New error codes: provider_secret_required - Server keys: `npx gemmein keys create --name --env development|live --access :,…` makes a server key with exactly that access on one browser approval; the key goes to the secrets file, never the screen. `keys` lists them, `keys revoke` ends one. New error codes: key_name_taken, key_not_found, invalid_key_plan, no_environment - Server keys: `gemmeinServer(sk, { expects })` compares the key's access with what the app needs before the first call; too narrow or too broad throws key_scope_mismatch with err.missing or err.extra. whoami returns the key's access. New error codes: key_scope_mismatch - Domains: `npx gemmein domains add ` and `domains remove ` run on one browser approval; add prints the DNS records as one list and the one thing to do. New error codes: domain_exists - Allowed addresses: Once an app is live, a page on one of its verified domains that still calls with the Development key is answered, and Live's Problems says so once a day per domain (`dev_key_on_live_site`); `npx gemmein domains` and `npx gemmein check` print it. Switch the live build to the Live key (`npx gemmein keys app`). - Payments: `welcomeBonus` on a plan or product adds credits once per member, only on their first paid payment in the app. No relay needed. - Payments: grant_credits takes `ref`: once per person and ref, forever. - Payments: A plan-start email (gemmein/emails/plan-start.html) goes out when a paid plan or its free trial begins; each paid subscription invoice writes a receipt (in the app's receipts collection, when it keeps one); a Payment Link return carries a display-only ?paid= marker for the thank-you screen. - Members: A sign-in from a new device or country emails the member "New sign-in to " (naming the country, never the address) with a single-use sign-out-everywhere link. Not on their first sign-in ever, read from the person's own record, so a person back after months away is still told; the owner can switch it off. - Members: currentUser() says why a stored session stopped working: ended "expired" or "revoked". - Members: g.auth.onSignedOut(listener) is the one place to route to sign-in: it fires when the session ends however it ended (reason "signed_out", "revoked" or "expired"), after the SDK has cleared its token, and returns a function that stops listening. - IP addresses: The founder sees each signed-in device's IP address and country on the person's page, and each security event's IP address in Logs, with a Copy button. The person's own devices() and the new-sign-in email name the country only. A CLI approval page shows the full IP address the request came from. - IP addresses: An IP address is removed after 30 days (the event stays in Logs without it). The person's export carries the addresses still inside 30 days (sessions and signIns); deleting the person removes the addresses on their events, and the events stay. - IP addresses: When a session ends, its device record (label, country and IP addresses) is deleted at once and leaves the list; the sign-in event in Logs keeps the device label and country. - Server SDK: The server client's update takes { published, ifVersion }, and its get, list, create and update are typed: collection("feed"). - Collection features: An open field can be "one per person of a, b, c" (polls, RSVPs): each person holds one choice, set with open(id).choose(field, choice); the record carries a count per choice. - Collection features: The app is a party on a direct collection: your server writes { from: "app", for: personId } and a member replies { for: "app" }; your human answers from the dashboard. - Email and domains: Designing the app's emails is a default build step; `npx gemmein check` and the go-live list warn when emails still use Gemmein's plain design. The purchase email for a credit pack says it's credits. - Relays: On every receiver scheme the relay chooses whose secret it is: Gemmein makes it, or you paste the sender's. A pasted secret is plain ASCII, not a repeated pattern, and at least 24 characters (22 when it mixes upper- and lower-case letters and digits); a Standard Webhooks secret is whsec_…. A stripe receiver always uses Stripe's own. - Relays: A relay's trigger kind can change freely under gemmein dev; once synced, the cloud refuses the change and names the two ways out (rename it, or `npx gemmein sync --prune` in Development). New error codes: trigger_kind_fixed - Relays: signedPayload rules, checked at save: the placeholders are {timestamp}, {url}, and ONE of {body}, {params} or {token}, which ends the text. Each appears once. Letters, digits and . : _ - go around them, at most 40 characters. A digit or {params} never comes right after {timestamp}. {timestamp} must appear when a timestamp is named and needs a source; {token} needs tokenField. A signature inside the body never signs {body} or {params}. timestampHeader, timestampKey and timestampField: one at most. signatureKey and timestampKey never share a name. The separator is , or ;. A break is refused with 400 invalid_definition, saying which and how to fix it. A recipe that saves but differs from how the sender signs answers 401 bad_signature on every event. - Relays: A receiver whose recipe is ed25519 or ecdsa-p256-sha256 checks the sender's PUBLIC key. Paste it on the relay's page or pipe it to `npx gemmein relays secret `, as the sender shows it: hex, base64 or a PEM block. Gemmein never makes one: asking answers 400 provider_secret_required. A private key, a key of another kind, or anything that is not a key is refused with 400 invalid_input. A public key is never a secret: on an HMAC recipe a PEM or DER key is refused, and changing a recipe between a public key and a secret always clears the stored value in the same write, on save and on Promote; a paste racing such a change answers 409 version_conflict and stores nothing; an HMAC recipe that holds a key anyway refuses every event and raises the Problem relay_key_as_secret. A public-key change is on Logs by its fingerprint, never the key. New error codes: provider_secret_required, invalid_input, relay_key_as_secret - Relays: One rule for relays whose sender doesn't sign the event itself: a signature recipe that signs only a token ({token} ends signedPayload, as Mailgun signs) and the shared_token scheme. By default such a relay only writes records and calls your URL. Granting (grant_access, grant_credits, grant_plan, fulfil_product, start_run), taking away (revoke_access, revoke_plan, refund_product), emailing the person (email_person) and creating people (a map naming person_email) answer 400 invalid_definition, on the dashboard, the CLI, sync, gemmein dev and Promote (its dry run names it as not carried). person_id and person_token never create anyone. To let a relay act anyway, set trigger.trustUnsignedEvents: true on it: the save answers a one-line warning (the CLI and sync print it once), the relay's page shows it beside the setting, Logs records the change before and after, and Promote carries it with the warning in its dry run. The opt-in is refused on a sender that signs the event. A record a relay writes never fires a data_change relay. A stored relay that breaks the rule refuses every event with 401 bad_signature and raises the Problem relay_unsigned_event. An event that arrived unsigned never acts later: if the relay changed to act without the opt-in before it ran, it ends without running, and a replay of it answers 409 unsigned_event. With the opt-in on, a save that widens what a captured call buys answers the warning again. The Go live and Promote previews list each opted-in relay with its warning and each relay they won't copy. The RevenueCat and shared-token templates carry the setting, with the warning in their note. New error codes: invalid_definition, relay_unsigned_event, unsigned_event - Relays: {url} in a recipe is the receiver URL exactly as the relay's page shows it, never the Host header. A call that adds anything after it, a query string included, answers 401 bad_signature. A receiver reads a form post (application/x-www-form-urlencoded) as its fields, so a map names them directly. - Relays: A recipe whose signed text is not the raw body ({params} or {token}) accepts each signature once, still for 24 hours after the relay is deleted. A {token} recipe (Mailgun) does not authenticate the event: a captured signed call verifies once with any body within 5 minutes, so check such an event with the sender's API before granting. Senders that sign no timestamp (GitHub, Intercom, Square, Twilio) are deduped by event id, or by body within the UTC day. - Relays: A signature header listing more than 8 signatures is refused with 400 too_many_signatures. A relay saved before the rename to signature answers 401 scheme_renamed until its definition is saved with "scheme": "signature". New error codes: too_many_signatures, scheme_renamed - AI tools: Under gemmein dev, your own pipeline can hand back files at a loopback http URL (the cloud still takes https only); the SDK's pipeline helper fails the run, and the hold returns, when the hand-back is refused. - AI tools: Every AI answer passes at most 32 MB; past it the answer is cut where it stands and costs its ceiling. - Test kit (Development): `npx gemmein members add ` makes test members at @.gemmein.test; `npx gemmein mail` reads their sign-in codes and every email the app sends them, with headers and SPF/DKIM/DMARC. Development only; their mail never leaves Gemmein. New error codes: test_kit_development_only, test_members_full, test_kit_capped, not_a_test_member - Test kit (Development): The founder's actions on test members from the CLI: approve, send-back, suspend, unsuspend, sign-out --everywhere. New error codes: changed_since_read - Test kit (Development): `npx gemmein pay --as ""` pays through the app's real checkout with Stripe's test card and waits for the webhook; `npx gemmein refund ` refunds it. They run only on a test-mode Stripe key and are refused on a live one. Two optional rows on a sandbox key serve them: Charges and Refunds (Write) and Checkout Sessions (Read). New error codes: test_pay_live_key_refused, test_pay_local, test_pay_unavailable, test_refund_refused, test_refund_outcome_unknown, test_checkout_unknown, payment_not_found, unknown_item, stripe_not_connected - Hosted connector: AI apps with no terminal — ChatGPT, Claude, Lovable, Replit, Bolt, v0 — add https://api.gemmein.com/mcp and build and run a Gemmein app from the chat. The ten tools of `npx -y @gemmein/mcp` need no account. One approval on the same page `npx gemmein login` uses lets the connector build in Development: pull and sync the gemmein/ files (the files win, the diff shown first), or declare collections, plans, relays, AI tools and emails one at a time; test members, test mail and sandbox test payments. Live is never part of it: each Live change is approved on its own, with the code Gemmein emails — except going live, where paying for the plan is the confirmation. Reading Live is its own approval line, and every read is on Logs. Secret keys are shown once in the browser, never in the chat. The connection lasts 30 days and is revoked on Approvals. `npx gemmein dev` serves the same /mcp on this computer. New error codes: sign_in_required, invalid_token, insufficient_scope, key_in_browser, token_in_url, host_refused, origin_refused - Hosted connector: Six read-only Live tools on the live:read grant answer "how is my app doing?" without the dashboard: live_overview (people and revenue this month against last, errors in the last 24 hours, what waits on the founder, paused or not), live_errors (each code with counts, first and last seen, an example Logs row and its docs page), live_usage (meters against the plan, AI calls, credits used and given, emails sent), live_webhooks (relay runs, retries, each failure's reason), live_emails (sent, bounced, complained, deferred per template) and live_inbox (conversations, unanswered first; one in full). Each read is on Logs and changes nothing. New error codes: unknown_relay, thread_not_found - Hosted connector: inbox_reply answers a customer inside an existing Inbox conversation, from the app's support address, exactly as the founder's reply — its own approval line (live:inbox_reply, "Reply to your customers' messages"), the one standing Live action. Never after an automatic message (inbox_auto_reply), at most 3 AI replies in a row (inbox_reply_cap), never while the last message can't be read (inbox_unreadable), at most 60 an hour per connection and 300 per app (inbox_reply_limited). Only to senders whose email passes DMARC, as the receiving provider saw it (inbox_unauthenticated otherwise; you can reply to anyone yourself); at most 10 a day per conversation (inbox_thread_limited) and 5 an hour per address (inbox_recipient_limited); Live's side of the Inbox only. Each reply shows in the Inbox as sent by your AI. New error codes: inbox_auto_reply, inbox_reply_cap, inbox_unreadable, inbox_reply_limited, inbox_unauthenticated, inbox_thread_limited, inbox_recipient_limited - Server keys: A server key can hold the support Inbox: `--access inbox:read,inbox:reply` (the dashboard's "Read your customers' support messages" and "Reply to your customers' support messages"). gemmeinServer(sk).listInboxThreads(), getInboxThread(id) and replyInboxThread(id, text) — GET /server/inbox/threads, GET /server/inbox/threads/:id, POST /server/inbox/threads/:id/reply — run the same guard and caps as the connector; a key's reply shows as sent by the key. New error codes: inbox_auto_reply, inbox_reply_cap, inbox_unreadable, inbox_reply_limited, thread_not_found, inbox_unauthenticated, inbox_thread_limited, inbox_recipient_limited - Errors: A refusal in Development, from a secret key, the CLI or the MCP connector carries `docs`, the code's own page: `{ code, message, docs: "https://docs.gemmein.com/errors/" }`. `GemmeinError` has `.docs` (JS and Swift). A request made with the Live app key is answered without it, so nothing a customer can read names Gemmein. - What changed: This feed: `npx gemmein changes [--since ] [--json]`, the MCP tool `changes`, and https://docs.gemmein.com/changes.txt (and .json). The guide opens with its version and what changed. - Founder sign-in: A founder can turn on an authenticator app (TOTP: 6 digits, 30 seconds) in the dashboard's Settings › General, each person on an account for themselves. With it on, signing in to the dashboard asks for its code after the emailed code, and so does deleting a Live collection, a domain of a live app or the account, approving such a delete for an AI, and turning it off. Ten single-use recovery codes are shown once and work in its place. Nothing in Development asks; prices, plans, keys, Stripe, going live and promote never do. Ten wrong codes in a row slow the next try (1 minute up to a day) and email the founder — never a lockout. For an AI: a Live delete you ask for waits on your human's Approve as before — that page may now ask them for the code. New error codes: app_code_required, app_code_invalid, app_code_sign_in_expired, app_code_already_on, app_code_not_pending, app_code_not_on, app_code_wait ------------------------------------------------------------------------ This record starts after Gemmein 0.22.1. ------------------------------------------------------------------------ No longer true (if you remember one of these, it is wrong now): - Was: Gemmein runs nothing for you: it has no compute of its own, and a relay only passes events along. Now: Gemmein runs its own AI tools and relays. A relay receives the event, maps fields and carries out Gemmein's own actions; it never runs code you write or reshapes data with custom logic. An external pipeline tool and your own server code run on your own host. - Was: There are no native mobile SDKs. Now: Native mobile SDKs exist: @gemmein/sdk/expo (Expo and React Native) and GemmeinSwift (Apple platforms). - Was: Gemmein charges for each active person. Now: Each Live app pays a flat price for the tier its founder picks; active people are a limit, never a price, and there is never a per-person invoice line. - Was: Every payment lands in a single Stripe account, and payments are Stripe-only. Now: Money always lands whole in the founder's own payment provider account: Stripe is built in, others come through a relay. Gemmein never holds it. - Was: The free (default) plan can't carry a credit allowance. Now: The free (default) plan may carry credits.perPeriod (granted once per person per calendar month, UTC, at their first spend) and/or credits.once, a welcome allowance for a person who never held a paid plan. - Was: People can't upload their own audio for transcription. Now: A transcribe tool's file input takes audio the person uploads with g.ai.upload(tool, file) (mp3, m4a, wav, webm, ogg, flac; 25 MB, 14 MB on Google), a file they already own, or an https URL. - Was: Signing in to your own app as its owner lifts your role, so the app can show you everyone's records. Now: In the app the owner is an ordinary customer and reads only what the rule gives them. The Gemmein dashboard is the admin view; an in-app admin screen goes through your own server with a read-only secret key. - Was: sends_disabled means the owner switched in-app sending off. Now: sends_disabled means direct messages are off for this collection; addressed records are sent from the dashboard or a relay. - Was: A secret key can only read and update records; it can't create or delete them. Now: A secret key gets one of three levels per collection (reads only; reads and writes; or reads, writes and deletes), or is a full key for every collection. A key that writes creates and updates records under any rule; name whose record it is with { for } or { from }. - Was: You can paste your own Stripe Payment Link and signing secret instead of connecting a key. Now: Selling through Stripe needs the restricted-key connection on the Payments page; Gemmein makes each Payment Link. - Was: Live storage is 10 GB per app. Now: Live storage is flat per tier (5 GB, 50 GB or 150 GB) plus 50 GB per storage pack; Development is capped at 1 GB. - Was: The CLI links to the cloud with a CLI key your human copies from the Setup page. Now: `npx gemmein login` prints a link and a code; your human presses Approve in the browser. No key is ever shown, copied or pasted. - Was: Live actions wait for you to type a confirmation. Now: Each Live action is approved in the browser, once, for exactly the plan shown; there is nothing to type. - Was: Gemmein picks up a collection's fields from the records you write in Development. Now: Every community, shared and direct collection declares its fields in gemmein/collections/.json; nothing is guessed from test data. - Was: Signing in on a second device signs the person out of the first. Now: Members stay signed in on up to 10 devices, each listed and signed out on its own; founders keep one dashboard session. - Was: A plan-selling app needs its customer portal set up in Stripe and the link pasted on Payments. Now: Gemmein sets up Stripe's customer portal from your plans; g.subscriptions.manage() opens it. There is nothing to paste. - Was: A chat AI tool runs only on the three hosts: OpenAI, Anthropic and Google. Now: A chat AI tool runs on OpenAI, Anthropic, Google or any endpoint that speaks the OpenAI Chat Completions format (provider openai-compatible: a base URL, a key and the endpoint's own model ids). - Was: Going live drops localhost and every unverified domain for every key; before go-live a page works only on a listed domain or localhost. Now: The rule is per key: the Development app key works from any secure (https) address and from localhost, before and after go-live, with nothing to list; the Live app key works only on verified domains. - Was: Live apps pay one plan whose price steps from $50 up to $150 a month with the previous month's active people. Now: Each Live app pays a flat price for its tier: Launch $10, Live $40 or Growing $100 a month (or $100, $400, $1,000 a year); past 15,000 people, talk to Gemmein.