Skip to content
AXOWLDevelopers
Quickstart

Changelog

What changed on the surface you integrate against: the public API, token format, standards support, and SDKs. Internal dashboard work is not listed here unless it changes something you call.

Dates are the date the change went live on the development environment (testapi.axowl.com).


2026-11-13 — App icon corners and the bot check box

Section titled “2026-11-13 — App icon corners and the bot check box”
  • App group update (PUT /api/org/{slug}/app-groups/{groupId}) takes iconCornerRadius (percent, 0 square to 50 circle) and botCheckHidden (hide the Cloudflare Turnstile box; Pro and above, 403 UPGRADE_REQUIRED otherwise). See Applications.
  • GET /api/public/apps/{clientKey}/login-methods returns appGroup.iconCornerRadius and methods.botProtectionHidden. The bot check box is shown by default; it used to be hidden unless Cloudflare needed a click. The check itself has not changed.

2026-11-13 — Employee sign-in policy and same-domain verification

Section titled “2026-11-13 — Employee sign-in policy and same-domain verification”
  • New GET/PUT /api/org/{slug}/sso-login-policy: accept employee invitations only through the company IdP (staff_invite_sso_only), and choose when the organization sign-in page offers “Registered employee / Guest” (staff_login_basis). See Self SSO domains.
  • Invitation accept routes return 400 sso_required when the organization requires the company IdP and the invitee hasn’t confirmed with it yet.
  • A domain your organization already verified is verified at once when registered again for the other kind (email ↔ app).

2026-11-12 — SDK releases: audience is required to verify a token

Section titled “2026-11-12 — SDK releases: audience is required to verify a token”

Published today: @axowl/sdk-backend 0.4.0, @axowl/sdk-core 0.3.5, @axowl/sdk 0.3.4, @axowl/sdk-analytics 0.1.2, Python axowl-sdk 0.2.0, Java com.axowl:axowl-sdk 0.2.0, .NET Axowl.Sdk.Identity.Client 0.2.4 and Axowl.Sdk.Korean.Identity.Client 0.1.3.

Breaking — set audience before you upgrade a backend SDK. Token verification now requires your client’s key (client_…) as audience, so a token issued to another app is never accepted:

  • @axowl/sdk-backend 0.4.0: axowlMiddleware({ …, audience }) throws when the app starts without it, and verifyTokenAsync throws a configuration error. The decode-only verifyToken() and getAuth() are removed — use verifyTokenAsync and getAuthAsync. See Quickstart.
  • Python axowl-sdk 0.2.0: verify_token and AxowlAuth(...) raise ValueError without it. See Python SDK.
  • Java com.axowl:axowl-sdk 0.2.0: new AxowlTokenVerifier(config) throws IllegalArgumentException without it. See Java SDK.

Fixes.

  • @axowl/sdk-core 0.3.4+: an app member’s session survives the SDK’s own calls (getUser, can, refresh, logout, 401 handling), and the default baseUrl points at https://api.axowl.com.
  • @axowl/sdk 0.3.4: a network failure no longer erases the device’s passkey hint in <SignIn />; the provider reaches the signed-in state after the hosted round trip.
  • @axowl/sdk-analytics 0.1.2: the referrer is sent as its origin only, and page() paths drop their query string.
  • .NET Axowl.Sdk.Identity.Client 0.2.4: documentation comments only.

2026-11-12 — End-user MFA can be completed on every sign-in path

Section titled “2026-11-12 — End-user MFA can be completed on every sign-in path”

Hosted sign-in page. With Require MFA for end-users on, social sign-in, SSO and passkey sign-in now show the code step on the hosted page (login.axowl.com) and then return to your app. Before, only the email link and “Continue with Axowl account” did; the other paths ended in an error. The step also offers Send again (once a minute) and tells the person when the first code could not be sent. Your app needs no change — it still receives a finished session.

JavaScript SDKs — @axowl/sdk-core 0.3.5, @axowl/sdk 0.3.4. For apps that run their own sign-in screen:

  • client.mfaChallengeResend(mfaToken) is new (the endpoint POST /apps/{key}/auth/mfa/challenge/resend already existed).
  • mfaEnrollSend, mfaChallengeResend and mfaVerify now throw an AxowlError with a code to branch on and a sentence you can show: MFA_CODE_INCORRECT, MFA_SESSION_EXPIRED, MFA_COOLDOWN, MFA_TOO_MANY_SENDS, MFA_RATE_LIMITED, MFA_INVALID_NUMBER, MFA_REGION_NOT_SUPPORTED, MFA_SMS_UNAVAILABLE, MFA_SEND_FAILED, MFA_NOT_ALLOWED. They used to carry the raw response body as the message.
  • The sign-in response type gains smsDelivered, smsChannel and smsError.
  • <MfaChallenge /> is exported from @axowl/sdk: the whole second step (phone number the first time, 6-digit code, re-send, errors) as one component. <SignIn /> uses it after a passkey sign-in.

The API itself did not change.


2026-11-11 — Free plan: one database connection, no Strix; Always-On fixes

Section titled “2026-11-11 — Free plan: one database connection, no Strix; Always-On fixes”

Plan limits on data sources. A Free organization can hold one database connection in total and cannot use Strix. POST /api/org/{slug}/app-groups/{groupId}/data-sources and …/reconnect answer 403 PLAN_DB_LIMIT with { limit, requiredPlan } at the limit; POST …/strix, reconnecting a Strix row and turning Always-On on answer 403 STRIX_PLAN_REQUIRED. Existing Strix databases of Free organizations keep working and can still be turned off, disconnected and deleted. See Database Management → Pricing shape.

Always-On is charged when you turn it on. POST …/data-sources/{id}/tier with alwaysOn: true now takes the rest of this month’s $15 (by remaining days) from Axowl Credit before switching, answers 402 CREDIT_REQUIRED with required when the balance is short, and returns charged. Previously only the next monthly run charged it.

Turning Always-On off is scheduled for the end of the month. POST …/data-sources/{id}/tier with alwaysOn: false no longer switches at once: it answers { alwaysOn: true, endsAt, scheduled: true } and the database goes back to Serverless at endsAt (the 1st of next month, UTC). Sending alwaysOn: true before then cancels the scheduled turn-off without a charge. List rows carry alwaysOnEndsAt.

Paid invoices. Every payment (card top-up, auto top-up, add-on, plan change, Always-On) gets a paid invoice, emailed to the owners as a PDF. GET /api/org/{slug}/billing/paid-invoices lists them and …/paid-invoices/{id}/pdf downloads one (dashboard session, view.billing).

List response fixed. GET …/data-sources now returns isAlwaysOn, alwaysOnDowngradedAt and purpose on every row. They had been dropped from the response, so the dashboard showed every database as not Always-On (with no way to turn it off) and never showed the staging badge.

Promotion checklist. GET …/app-groups/{groupId}/promotion-checks items carry appId for callbacks and legal — the first application to fix.


Section titled “2026-11-08 — Fledgeplan preview links and automatic deploys are Pro+”

The app’s Cloudflare connection and deployment target (Dashboard → Application → Repository) now need Pro+ or higher: POST /api/org/{slug}/applications/{appId}/cloudflare and POST …/deployment-target answer 403 UPGRADE_REQUIRED with feature: "feature.fledgeplan.preview_deploy" on lower plans. Reading and disconnecting are unchanged, and the rest of the agent loop (repository connection, cloud runs, PRs, passkey approval, merge) works on every plan. See Build an app with an agent → Preview links and automatic deploys. The pricing page lists the row.


2026-11-08 — Sign-out can return to your app; GitHub App installations work end to end

Section titled “2026-11-08 — Sign-out can return to your app; GitHub App installations work end to end”

Post-logout return URLs are now one list for every sign-out path. POST /api/public/apps/{clientKey}/auth/logout accepts returnUrl and answers redirectUrl when the URL is listed under the application’s post-logout return URLs (Dashboard → Application → Authentication, now next to the Callback URLs). OIDC end_session_endpoint checks the same list. An unlisted URL answers 400 RETURN_URL_NOT_ALLOWED and does not sign the person out. JS SDK @axowl/sdk-core 0.3.2 / @axowl/sdk 0.3.2: logout({ returnUrl }) and signOut({ returnUrl }); the SDK now calls the app end-user logout endpoint (it previously called the dashboard session endpoint, so end-user sessions were not revoked and EndUserLoggedOut was not emitted).

GitHub App installations. The App JWT Axowl signs for installation access_tokens now carries iat; without it GitHub answered 401, so a repository connected through the GitHub App (as opposed to an access token) could not list repositories or run Fledgeplan in the cloud. The repository card also starts with step 0, “create a repository on GitHub first”.


2026-11-08 — Top-up volume bonus removed

Section titled “2026-11-08 — Top-up volume bonus removed”

Card top-ups (POST /api/org/{slug}/billing/topup) no longer grant bonus credit at any amount; the credit added equals the amount paid. The bonusCredits field in the ledger contract is still sent, now always 0. Welcome credit on sign-up is unchanged. Refunds keep the cost-recovery fee only — there is no bonus clawback line any more. See Invoices → balance.


2026-11-08 — Verify reports whether the seal actor is a human or an AI agent

Section titled “2026-11-08 — Verify reports whether the seal actor is a human or an AI agent”

POST /v1/integrity/verify adds actorType (proto actor_type = 9): human, ai, service or group — the kind of the badge recorded as the seal actor. Axowl reads it from the badge when sealing; seal requests are unchanged and nothing new is sent. Empty when no actor was recorded or the position was sealed before this date. Not part of the hashed record — existing hashes are unchanged. .NET: VerifyResponse.ActorType (SealActorType, Unknown when empty). See Integrity → actor type.


2026-11-06 — Gateway connections confirm with a test payment; one collection method and currency per app group

Section titled “2026-11-06 — Gateway connections confirm with a test payment; one collection method and currency per app group”

A new gateway is pending until it passes a test payment. Connecting a gateway (POST …/payment-gateway) or reusing one (…/reuse) now creates a pending connection that is not used for sales; the current collection keeps running. POST …/payment-gateway/test-payment then …/test-payment/{paymentId}/verify charges your own card a small amount (100 KRW, KG Inicis 1,000 KRW; Razorpay ₹1), refunds it at once and — when it passes — replaces the current collection in one step. POST …/payment-gateway/pending/discard drops it. On the live platform a test key can be tested but not confirmed (TEST_CHANNEL_CANNOT_CONFIRM). See Applications → own gateway.

Test payment details. KG Inicis needs the buyer’s name, email and phone: 409 CUSTOMER_INFO_REQUIRED with missing, or send name / phoneNumber with the request (used for that payment only). Razorpay test payments go through Razorpay Checkout and are refunded through the Razorpay refund API.

No switching while there are products or subscribers. Changing an app group’s payment service — Application payment integration ↔ own gateway, or one gateway or store for another — is refused while the app group has products (409 PRODUCTS_EXIST) or live subscribers on the service it leaves (409 LIVE_SUBSCRIPTIONS / 409 SUBSCRIPTIONS_UNKNOWN). New GET …/payment-gateway/live-subscriptions returns the counts. Switching to Application payment integration also needs a ready payout account (409 STRIPE_NOT_READY).

One price currency per app group. POST …/products accepts only the payment service’s currency (own gateway → KRW or INR, otherwise USD); anything else is 409 CURRENCY_FIXED.

Collection method per app group. Creating an app group (POST /api/org/{slug}/app-groups) takes collectionRail — none, app_payment (Application payment integration), own (your own gateway) or collect_on_behalf (not offered yet, 409 NOT_OFFERED); the older monetizationEnabled: true still means app_payment. New app groups always start with collection off. …/payment-gateway/payment-service takes mode: "app_payment" (older axowl still accepted) or own; it chooses the method and turns collection off, and no longer checks the payout account — that check moved to turning collection on.

Turning collection on needs a confirmed method and a product. PUT …/app-groups/{groupId} with monetizationEnabled: true answers 409 PAYMENT_SERVICE_REQUIRED (no method), 409 PAYMENT_SERVICE_NOT_CONFIRMED (own gateway not past its test payment), 409 STRIPE_NOT_READY (payout account not ready) or 409 PRODUCTS_REQUIRED. When a gateway passes its test payment while the app group has no products, collection stays off. Products can’t be added while your own gateway waits for its test payment (409 PAYMENT_SERVICE_NOT_CONFIRMED). Removing the last product turns collection off.

Disconnecting needs no products and no subscribers. POST …/payment-gateway/disconnect answers 409 PRODUCTS_EXIST / 409 LIVE_SUBSCRIPTIONS / 409 SUBSCRIPTIONS_UNKNOWN before the seal; afterwards the app group has no collection method and collection is off. GET …/payment-gateway/live-subscriptions adds rail, currency, collectionOn, ownConfirmed, and product with ?productId=.

Marketplace: free or paid. PUT …/marketplace with publish: true takes free (required the first time, 400 FREE_REQUIRED). A paid listing needs a confirmed collection method, collection on and a product, else 409 MARKETPLACE_PAID_NOT_READY with missing; paid → free needs zero live subscribers. GET /api/public/marketplace/apps adds free and salesPaused per app.

Money-line guard. A request for one money line that reaches a payment function of the other line fails with 409 MONEY_LINE_VIOLATION ({ error, detail }, no internal names) instead of running.

Products need a collection method; imports follow the same rules. When an app group’s collection method is set to none, POST …/products, …/products/bulk and …/products/import answer 409 PAYMENT_SERVICE_REQUIRED (app groups created before 2026-11-06 without a method keep working in USD). …/products/import now applies the same currency rule per row and refuses while your own gateway waits for its test payment (409 PAYMENT_SERVICE_NOT_CONFIRMED); rows in another currency are not imported and come back under skipped (name, currency, reason: "CURRENCY_FIXED").

Free marketplace apps. An app listed with free: true installs without a plan (no 402 PAYMENT_REQUIRED), and …/marketplace/apps/{groupKey}/checkout / …/change-plan answer 409 APP_IS_FREE. salesPaused is true only for an app listed as paid (free: false) whose collection is off, that has no products, or that has no plan buyers can pick; apps listed before 2026-11-06 without a free/paid answer are never paused.

Revenue per currency. GET /api/org/{slug}/marketplace/revenue adds currencies (the same shape per currency sold, own-gateway KRW/INR sales included and never mixed into dollars), fx.krwPerUnit and displayCurrencies.

  • MFA texts get a 3-day grace when credit runs out: instead of refusing at once, Axowl emails the organization’s owners and keeps sending end-user MFA codes for 3 days (up to 200 texts). Those texts are charged when you top up. After that, codes stop with sms_unavailable. MFA send errors now also carry a plain-English message.

  • Plan change on an own-gateway seller: POST /api/org/{slug}/marketplace/apps/{groupKey}/change-plan answers 409 PLAN_CHANGE_NOT_SUPPORTED when the seller collects through its own gateway (PortOne or Razorpay). Cancel and buy the new plan instead.

  • Market country on every Stripe door: turning collection on (PUT …/app-groups/{groupId} with monetizationEnabled: true), POST …/payout-session and POST …/payment-gateway/payment-service with mode: "axowl" now answer 409 MARKET_NOT_OFFERED when the app group’s local market is outside the Stripe countries — the same rule as creation.

  • Razorpay connection add-on in use: turning off addon.razorpay_gateway while an app group is connected to Razorpay answers 409 ADDON_IN_USE. Disconnect Razorpay first.

  • SMS credit gate under consolidated billing: the 402 SMS_CREDIT_REQUIRED body returns balanceUsd: null and paidByAnotherOrg: true when another organization pays the bill. The plan check (403) for mfaRequired now runs before the credit check.

  • Own-gateway renewals: a declined renewal is retried with growing gaps (1, 2, 4 … up to 24 hours) instead of every hour.

2026-11-05 — Razorpay connection add-on; Stripe at the top of the gateway list

Section titled “2026-11-05 — Razorpay connection add-on; Stripe at the top of the gateway list”

Razorpay connection add-on ($3/month). Connecting Razorpay as an app group’s own gateway — POST /api/org/{slug}/app-groups/{groupId}/payment-gateway with provider: "razorpay", or reusing a Razorpay connection on another app group — now needs the addon.razorpay_gateway add-on ($3/month per organization, buyable on every plan). Without it the call answers 402 ADDON_REQUIRED with addon, before Razorpay is contacted. Existing connections keep working. There is still no take rate on your own gateway.

Stripe in the gateway list. For organizations in the 44 Stripe countries, the dashboard’s gateway list shows Stripe first. It leads to Application payment integration (your own Stripe account) — there is no key-based Stripe connection, and the API is unchanged.

2026-11-04 — App group stages, your own social keys, account deletion, key rotation

Section titled “2026-11-04 — App group stages, your own social keys, account deletion, key rotation”

Internal apps, API-only promotion, market-country payments. serviceModel accepts internal (an app used only by your own staff: no marketplace, no scope, no category). Promotion checks skip domain and legal when every client is API or machine-to-machine. Creating an app group with monetizationEnabled: true and a local market outside the 44 Stripe countries answers 409 MARKET_NOT_OFFERED. Connecting your own gateway to an app group with a local market accepts only that country’s gateway (Korea → PortOne, India → Razorpay); others answer 409 MARKET_NOT_OFFERED. The client environment field is no longer shown in the dashboard; it never changed behavior — the app group’s stage does.

Your own SMS provider; service scope required. App groups can send end-user verification texts through their own Twilio or Telnyx account (/app-groups/{groupId}/sms-provider, verified by a test text) — no Axowl charge, and no fallback to Axowl when it fails. Creating a SaaS or PaaS app group now requires marketScope (400 SCOPE_REQUIRED).

End-user MFA texts are charged; top-up minimum $5. End-user MFA verification texts are now charged to the organization’s credit at $0.05 each (KRW account first when present). If the balance can’t cover a text, nothing is sent and the MFA send answers sms_unavailable. Turning on mfaRequired needs at least $5 of credit — 402 SMS_CREDIT_REQUIRED otherwise. The minimum credit top-up is now $5 (was $10).

Your own gateway from any country. POST /api/org/{slug}/app-groups/{groupId}/payment-gateway no longer requires the organization to be registered in the gateway’s country: any organization with a country set can connect PortOne or Razorpay. The 409 NOT_OFFERED country refusal is gone; 409 REGION_REQUIRED still applies when the organization has no country. The app-group creation dialog also adds Don’t collect payments, which creates the group with monetizationEnabled: false and no gateway.

Development and production stages. Every app group now has a stage: Development or Production. New app groups start in Development; app groups that existed before keep Production. In development, only testers you add can sign in (up to 100), payments run on test keys, and member emails are marked [DEV]. Promote with POST /api/org/{slug}/app-groups/{groupId}/promote after GET …/promotion-checks passes. See Applications → Development and production.

  • GET /api/public/apps/{applicationKey}/login-methods → appGroup.isPublic is now whether a new person can actually sign up (false in development), and appGroup.stage is new.
  • Creating or importing members in a development app group beyond 100 testers: 409 TESTER_LIMIT_REACHED (create) or a limit row status (import).
  • Starting a purchase in a development app group without a test gateway key: 409 LIVE_PAYMENTS_REQUIRE_PRODUCTION. Development app groups may connect test keys on the live platform.

Your own social sign-in keys per app group. GET/PUT/DELETE /api/org/{slug}/app-groups/{groupId}/social-credentials stores your own OAuth client for Google, Apple, GitHub, LINE, Kakao or Naver. When a key is stored, that app group’s sign-in uses only that key; otherwise Axowl’s shared key is used as before. Promotion to production requires your own key for every enabled provider. See Social login → Your own OAuth keys.

Deleting an account. The signed-in user can delete their own account: GET/POST /api/v1/users/me/account/deletion. Sign-in stops at once on every device; the account can be restored for 30 days with an email code (/api/auth/account-recovery/send · /confirm), then it is anonymized. Public page: axowl.com/account/delete. See User account → Deleting the account.

Signing key rotation without downtime. Rotating an organization’s signing key keeps the previous key in the JWKS and accepts it for 24 hours, so issued tokens keep working. Every key has its own kid. See OIDC → Key rotation.

Sign-in hardening.

  • Suspended, locked, deleted and pending-deletion accounts are refused on every sign-in path (passkey, email link, social, SSO, refresh) with ACCOUNT_SUSPENDED, ACCOUNT_LOCKED, ACCOUNT_DELETED or ACCOUNT_PENDING_DELETION. POST /api/auth/revoke-all ends every session and its access tokens.
  • Members are separate per app group: the same email in two app groups is two members with different sub. Member lists return memberScopeId and appGroupName.
  • An app group with no social providers enabled has social sign-in closed (it used to allow every provider). New app groups start with Google enabled.
  • A member removed from an app group cannot sign back in to it; OIDC refresh rechecks the member’s status and access.
  • End-user tokens carry jti and iat; an ID token (token_use: id) is refused as a bearer token. A token issued for one app is refused by every other app, including sibling apps in the same organization; app-group-scoped API keys only accept tokens of their app group.
  • The gateway accepts an RS256 token only when it is signed by the key of the organization in its iss, and its audience app belongs to that organization.
  • Partner assertions refuse reserved audiences and app-group-scoped keys.

2026-10-26 — KRW refunds, deleting an organization

Section titled “2026-10-26 — KRW refunds, deleting an organization”
  • KRW refunds (POST /api/org/{slug}/settings/addons/{addonKey}/krw-refund, POST /api/org/{slug}/settings/plan/krw-refund and their quotes) round the amount up to the won, never above what was paid minus what was already refunded. Charges still round down.
  • POST /api/org/{slug}/delete refuses while money is outstanding: an open statement for the month, refundable wallet balance, a negative wallet balance, or KRW account cash. 409 with errorCode: ORG_HAS_OPEN_BALANCE and detail. If the balance can’t be checked: 503 with errorCode: ORG_BALANCE_CHECK_UNAVAILABLE. Nothing is changed in either case.

2026-10-26 — App members: no subscription left charging

Section titled “2026-10-26 — App members: no subscription left charging”
  • DELETE /api/org/{slug}/endusers/{endUserId} no longer deletes the member when one of their subscriptions cannot be canceled: 409 MEMBER_SUBSCRIPTIONS_NOT_CANCELED with detail and failed. Nothing is changed; try again.
  • DELETE /api/org/{slug}/endusers/{endUserId}/groups/{groupId} (remove a member from an app group) now cancels that member’s subscriptions in that app group at the end of the paid period, with the same 409 when it cannot. The response carries subscriptions.
  • POST …/payment-gateway/disconnect also sets the gateway’s other live subscriptions (PortOne card keys) to end at period end; the response carries sunset.
  • A deleted member’s renewal is closed as canceled instead of sending “payment failed” emails.

2026-10-25 — Country required, stricter identity finalize, keys in the database

Section titled “2026-10-25 — Country required, stricter identity finalize, keys in the database”
  • Organizations without a country now get 409 REGION_REQUIRED from every payment door (payout-session, the collection switch, payment-gateway connect/switch) instead of NOT_OFFERED; POST /api/org/create and POST /api/public/v1/orgs reject a missing or non-ISO2 countryIso (400 DOMAIN_RULE_VIOLATION, COUNTRY_REQUIRED). Reference: Billing & monetization → Your organization needs a country.
  • Korean identity verification: finalize now checks that identityVerificationId was issued to the signed-in account and fails otherwise; the PortOne webhook safety net no longer guesses a ConnectedId. Start the check again if you see “This verification was not started by this account.”
  • Cookie-signing keys moved from the cache to the database — a server restart no longer logs anyone out (test environment: no action; existing keys were carried over). See Security → Session security.
  • Production enforces sealed devices and read verification by default. See Security → Device trust.
  • New npm package @axowl/sdk-analytics and a one-script-tag beacon.js for the existing collection endpoint POST /api/public/apps/{applicationKey}/analytics/events: page views and time on page (SPA routes included), referrer domain, UTM, sessions (custom.session_start / custom.session_end), opt-in clicks on data-axowl-track elements (custom.click), and your own named events from a fixed list. Batching, retry, offline queue and sendBeacon on close are built in.
  • Consent: requireConsent + setConsent(); Global Privacy Control / Do Not Track are respected by default. identify() takes the member id (token sub) and refuses emails.
  • Guide: Behavior analytics.

2026-10-25 — Stripe collection for the 44 countries; your own gateway is for its own country

Section titled “2026-10-25 — Stripe collection for the 44 countries; your own gateway is for its own country”
  • Collection through your own Stripe account is offered again to organizations in the 44 Stripe seller countries, at the plan’s rate on each payment (POST /api/org/{slug}/app-groups/{groupId}/payout-session). 409 SELLER_POLICY_REQUIRED until the seller policy is signed. Reference: Billing & Monetization → Connecting Stripe.
  • Your own gateway (payment-gateway) is offered only to organizations registered in that gateway’s country: PortOne in Korea, Razorpay in India. Other organizations get 409 NOT_OFFERED. Existing connections keep working.

2026-10-25 — Korea: PortOne with KG Inicis or Toss Payments

Section titled “2026-10-25 — Korea: PortOne with KG Inicis or Toss Payments”
  • POST /api/org/{slug}/app-groups/{groupId}/payment-gateway with provider: "portone" accepts pgProvider kg_inicis or toss_payments. Other PortOne PG values answer 409 PG_NOT_OFFERED before PortOne is contacted; …/reuse refuses to copy a connection on such a PG. Existing connections keep working. The dashboard lists PortOne first, marked “Recommended”, and offers only the two PGs. Reference: Applications → Own payment gateway.

2026-10-25 — Payment rules: seller policy before any payout door, live keys only, safe disconnect

Section titled “2026-10-25 — Payment rules: seller policy before any payout door, live keys only, safe disconnect”
  • POST /api/org/{slug}/app-groups/{groupId}/payment-gateway and …/payment-gateway/reuse now answer 409 SELLER_POLICY_REQUIRED until the organization owner has signed the seller policy — the same rule the Stripe payout account already had. The dashboard opens the signing modal in place.
  • On the live platform (api.axowl.com) a connection made with a test key (rzp_test_…, PortOne isTest) is not confirmed for sales (TEST_CHANNEL_CANNOT_CONFIRM), except for an app group in Development; test keys work end to end on testapi.axowl.com.
  • POST …/payment-gateway/disconnect first ends every live Razorpay subscription created through the connection (at period end). If any cannot be ended: 409 SUBSCRIPTIONS_NOT_CANCELED with reason and pending; success carries subscriptionsEnded.
  • POST /api/org/{slug}/settings/addons/{addonKey} answers 409 krw_org_use_krw for organizations in Korea that have not locked billing to USD (add-ons sold in won go through the won routes).
  • GET /api/public/v1/monetization gains ownGatewayEligible, ownGatewayProviders, ownGatewayTakeRatePercent (0) and sellerPolicyRequiredFor. Explanations in plain words: Billing & Monetization → Payment rules in plain words.

2026-10-25 — Behavior rollup counts sessions, clicks and your events

Section titled “2026-10-25 — Behavior rollup counts sessions, clicks and your events”
  • GET /api/v1/admin/audit/behavior/rollup gains events (your custom.<name> events: name, count, visitorsUpperBound, identifiedUsersUpperBound), clicks (target, count, visitorsUpperBound — elements marked data-axowl-track), daily[].sessions and totals.sessions. Session and click events no longer add to the page rows’ visitor ceilings. The console’s App Group › Behavior tab shows them. Reference: Behavior analytics.
  • GET /api/v1/admin/audit/analytics/rollup gains files (daily[] and totals: saved, opened, sharedViews, sealed, trashed) — file-box activity counted from the daily audit rollup; the console’s Audit › Analytics page shows it as a chart. Reference: Audit → Sign-in outcomes.

2026-10-24 — Race bells say which candidate they are

Section titled “2026-10-24 — Race bells say which candidate they are”
  • agent.wake data gains storyId, candidateIndex and candidateCount (both 1 outside a race). In a race the next line ends with “You are candidate k of N”. Receivers that fold repeated bells should fold on (reason, storyId, candidateIndex); the downloadable agent-wake-runner.py now does, so all N candidates start instead of one. Reference: Projects → A race of candidates.

2026-10-25 — Member tokens stay under /api/public; daily SMS limit for high-risk countries

Section titled “2026-10-25 — Member tokens stay under /api/public; daily SMS limit for high-risk countries”
  • An app member’s token (the one your app gets from sign-in) is now refused with 401 ENDUSER_TOKEN_SCOPE on every route outside /api/public/** — dashboard, /api/org/{slug}/…, /api/v1/…, admin. Every member route you integrate against is already under /api/public, so nothing changes for a correct integration. Reference: Applications → App member purchases → Where a member token works.
  • POST /api/public/sms/send: texts to numbers in the Limited and Restricted country groups count against a limit of 1,000 per organization per day; past it the call answers 429 daily_risky_destination_cap (limit, resetsAt) before any charge, and the organization owner is emailed at 80% and at the limit. Reference: SMS coverage → Texts you write.

2026-10-24 — App members can cancel a subscription

Section titled “2026-10-24 — App members can cancel a subscription”
  • New POST /api/public/apps/{applicationKey}/me/subscriptions/{orderId}/cancel (member token): cancels a subscription at the end of the paid period — the entitlement stays active until cancelEffectiveAt and the member is not charged again. Idempotent. Reference: Applications → App member purchases.
  • New POST /api/org/{slug}/app-groups/{groupId}/member-entitlements/{orderId}/cancel (owner-only): the seller’s side of the same cancellation; member-entitlements items now carry orderId. The dashboard shows a “Cancel subscription” button on App Group ▸ Payments.
  • DELETE /api/org/{slug}/endusers/{endUserId} now cancels the member’s live subscriptions and revokes their stored card keys; the response carries subscriptions.
  • SDK: cancelSubscription(orderId) in @axowl/sdk-core; useEntitlements() returns cancel(orderId) in @axowl/sdk. Reference: SDK overview → Selling plans.

2026-10-25 — Pricing copy says who pays the rate; Korean Package approval notice; USD plan refunds

Section titled “2026-10-25 — Pricing copy says who pays the rate; Korean Package approval notice; USD plan refunds”
  • The pricing page’s billing row now names the path the rate applies to: Application payment integration pays the rate on the amount paid (currently through Stripe, in some regions; more payment providers are added by region); connecting your own gateway (PortOne in Korea, Razorpay in India) carries no Axowl fee. The numbers did not change — the attribution did. Plain-words version: Billing & Monetization → Payment rules in plain words.
  • The Korean Package page shows an approval-pending notice while the won rail and KG Inicis unified identity wait for external approval (connection planned for late November), and the Korean tab explains Kakao review states for the sender profile and templates in one line each. Guide: Kakao AlimTalk.
  • The English Refund Policy now states that plan payments in USD are refunded manually by support through the refund process (no self-service button yet).

2026-10-24 — Project Management AI: free trial, and no silent revival after non-payment

Section titled “2026-10-24 — Project Management AI: free trial, and no silent revival after non-payment”
  • New POST /api/org/{slug}/app-groups/{groupId}/pm-ai/trial: one free trial per organization, one app group, up to 10 stories, no credit needed. The 11th story — from POST …/stories, axowl_pm_create_story or the drafts under axowl_pm_propose_epic — is refused with 402 PM_TRIAL_EXHAUSTED. GET …/pm-ai gains trialAvailable, trialActive, trialStoriesUsed, trialStoryLimit and suspendedForNonPayment. Reference: Projects → Project Management AI.
  • An add-on switched off for non-payment stays off until the organization connects again; disconnecting or deleting another app group no longer brings it back without payment.

2026-10-24 — Stories take their epic’s owner; PM AI is USD-only on every KRW route

Section titled “2026-10-24 — Stories take their epic’s owner; PM AI is USD-only on every KRW route”
  • PATCH /api/org/{slug}/projects/{projectId}/epics/{epicId} with ownerConnectedId now gives that owner to every story in the epic that has no owner yet (not Done, not archived) and wakes an assigned agent with story_assigned. Stories that already have an owner keep it. POST …/stories inside an epic without owners takes the epic’s owner. Reference: Projects → Stories and epics.
  • POST /api/org/{slug}/settings/addons/addon.pm_ai/krw-subscribe now answers 409 usd_only, like krw-checkout already did. PM AI is bought with Axowl Credit (USD) per app group.

2026-10-23 — Fledgeplan agents run on your GitHub Actions

Section titled “2026-10-23 — Fledgeplan agents run on your GitHub Actions”
  • Agents can run on your repository’s GitHub Actions instead of a computer of yours. Axowl installs .github/workflows/fledgeplan-agent.yml (no repository permissions, no checkout, workflow_dispatch only) and starts it when an agent has work. Claude usage stays on your own Anthropic account or subscription. Guide: Run Fledgeplan agents on GitHub Actions.
  • New POST /mcp/github-oidc/token and POST /mcp/github-oidc/report (authenticated with a GitHub Actions OIDC token), and the app group API /api/org/{slug}/app-groups/{groupId}/agent-runner (switch, runner, install, secret names, recent runs, run again).
  • Every stopped run carries a reason (claude_auth, actions_billing, queued_too_long, …) shown with what to do.

2026-10-23 — A race of candidates for one story

Section titled “2026-10-23 — A race of candidates for one story”
  • POST /api/org/{slug}/projects/{projectId}/stories/{storyId}/start takes candidates (1–3). With 2 or more, the agents are woken that many times and each hands in its own pull request.
  • New GET …/stories/{storyId}/race. POST …/stories/{storyId}/review takes candidatePullRequest; the other candidates are closed. Reference: Projects → A race of candidates.
  • MCP: axowl_pm_move takes pullRequest during a race. Reference: MCP → A race of candidates.
  • New GET /api/org/{slug}/projects/{projectId}/stories/{storyId}/cost: the Claude usage the story’s cloud runs reported (an estimate on your own account, not an Axowl charge). Reference: Projects → Claude usage per story.

2026-10-23 — Pull request line comments go back to the agent with a revision

Section titled “2026-10-23 — Pull request line comments go back to the agent with a revision”
  • POST /api/org/{slug}/projects/{projectId}/stories/{storyId}/review now appends the pull request’s new line comments to a revise / redo note (new optional includeLineComments, default true; response field lineComments). With comments to send, the note may be empty.
  • New GET /api/org/{slug}/projects/{projectId}/stories/{storyId}/pr-line-comments: the comments that would be sent. Reference: Projects → Line comments from the pull request.

2026-10-23 — Agents are woken when CI finishes

Section titled “2026-10-23 — Agents are woken when CI finishes”
  • agent.wake gains the reason ci_finished. After an agent attaches a pull request with axowl_pm_link, or axowl_pm_move to InReview is refused because CI is still running, Axowl watches that pull request and wakes the agent once its checks finish, green or red. A runtime such as claude -p no longer stops at “CI is still running” with nothing to wake it. Reference: MCP → Project work.

2026-10-23 — Seller list of app member purchases, identity check status

Section titled “2026-10-23 — Seller list of app member purchases, identity check status”
  • New GET /api/org/{slug}/app-groups/{groupId}/member-entitlements (owner-only): who bought what in an app group and whether they can use it now. Reference: Applications → App member purchases.
  • New GET /api/v1/identity/kyc/status: the signed-in person’s identity check state, with Stripe’s reason when the check needs another try. Starting a new check clears the old reason. Reference: Users → My profile.
  • New POST /api/public/apps/{applicationKey}/checkout: your app’s signed-in member buys an app group product (member token as Bearer). Stripe answers with a checkout URL; an own gateway (PortOne, Razorpay) answers with payUrl, the Axowl payment page that opens that gateway’s window. The member returns through GET /api/public/apps/{applicationKey}/checkout/return. Reference: Applications → App member purchases.
  • The payment engine reports each member order’s state to POST /api/public/v1/app-purchases (platform key), which keeps the member’s entitlement in step.
  • New GET /api/public/apps/{applicationKey}/plans (public plan list) and GET /api/public/apps/{applicationKey}/me/entitlements (the member’s purchases; member token).
  • SDK 0.4.0: <PricingTable>, <CheckoutButton>, useEntitlements(), usePlans() in @axowl/sdk; getPlans(), getEntitlements(), checkout() in @axowl/sdk-core; hasEntitlement(token, productKey) in @axowl/sdk-backend. Reference: SDK overview → Selling plans.

2026-10-22 — Own payment gateway: Razorpay (India), country rule, refunds

Section titled “2026-10-22 — Own payment gateway: Razorpay (India), country rule, refunds”
  • Razorpay. POST /api/org/{slug}/app-groups/{groupId}/payment-gateway takes provider: "razorpay" with the Key ID, Key Secret and webhook secret. Checkout answers carry razorpay (keyId, orderId or subscriptionId); confirm the Checkout result at POST /api/org/{slug}/marketplace/apps/{groupKey}/own-gateway/razorpay/confirm. Prices may be in INR. Reference: Applications → Own payment gateway.
  • Webhook path per gateway. POST /api/v1/webhooks/{provider}/brand/{token} — existing PortOne URLs (/portone/brand/{token}) keep working.
  • Country rule. A gateway is connected by organizations in its country or in a Stripe seller country; elsewhere payment-gateway and payout-session answer 409 NOT_OFFERED.
  • Refunds. Gateway refund webhooks reverse the marketplace commission in proportion; a one-time purchase refunded in full ends the order and removes the app.

2026-10-22 — Sealed files: proof of content, requester and approver

Section titled “2026-10-22 — Sealed files: proof of content, requester and approver”
  • Seal a file-box file. An agent asks with the new MCP tool axowl_request_file_seal; a person approves from the email with a passkey; Axowl re-reads the file and seals its SHA-256 into the integrity chain only if it still matches the approved hash. People can also seal directly from the console (POST /api/org/{slug}/files/{id}/seal, passkey). Storage → Sealing
  • Proof. GET /api/org/{slug}/files/{id}/proof?verify=true returns the sealed hash, a fresh re-hash, requester / approver (human or agent, passkey), the approval document and WORM status.
  • Share viewer. GET /api/public/share/{shareToken} adds a proof object; GET /api/public/share/{shareToken}/verify re-hashes on demand. People are not named there.
  • Sealed files cannot be trashed — console, DELETE /api/public/v1/files/{id} (409 sealed_file) and lifecycle rules alike. A new version is a new file; console uploads accept replacesFileId.
  • New audit event ORG113 org.file.sealed.

2026-10-22 — Fledgeplan agents: how-we-work, project rules, shared memory, self-review

Section titled “2026-10-22 — Fledgeplan agents: how-we-work, project rules, shared memory, self-review”
  • axowl_context now starts with the working agreement, the agent’s own role instructions, the project’s RULES.md (Documents tab) and the latest shared-memory entries. MCP → How we work
  • New MCP tools axowl_pm_remember and axowl_pm_memory — a shared memory per project (decision / convention / gotcha / handoff), visible on the board’s activity list.
  • axowl_pm_move to InReview now also requires selfReview (## Acceptance, ## Rules, ## Tests), checked before the pull request and CI.

2026-10-21 — Self SSO domains, separate from external SSO

Section titled “2026-10-21 — Self SSO domains, separate from external SSO”
  • New GET/POST /api/org/{slug}/sso-domains, POST …/{id}/verify, DELETE …/{id}: register company email domains (kind: "email") and app domains (kind: "app", per app group) and verify them with a DNS TXT record at _axowl-challenge.{domain}. Reference: Self SSO domains.
  • New public POST /api/public/sso/discover — finds where a work email signs in (found, mode: "external" | "axowl", redirect_url).
  • IdP connections: attaching a new email_domain now needs that domain verified first — 400 email_domain_not_verified.
  • App groups gain SsoLoginEnabled (default true) on PUT /api/org/{slug}/app-groups/{groupId}; when false, login-methods reports sso.enabled: false and SSO start answers sso_not_configured.
  • Billing: verified self SSO domains beyond the plan allowance are $10 per domain per month (invoice line category sso). External SSO connection pricing is unchanged.

2026-10-19 — Roost bank-grade controls: Auditor, log export, time policy, attestation

Section titled “2026-10-19 — Roost bank-grade controls: Auditor, log export, time policy, attestation”
  • Auditor role separation. A Roost Auditor is appointed or removed with an Owner-quorum passkey seal (POST /api/org/{slug}/roost/auditors/{connectedId}/assign|revoke). Once one exists, the organization audit log — query, detail, export URLs, verify, retention purge — answers 403 AUDITOR_REQUIRED to everyone else, Owners included. Role table: Roost → Roles.
  • Remote audit-log export. GET/PUT /api/org/{slug}/roost/log-export: syslog over TLS (RFC 5425) or RELP as one JSON line per event, and SNMPv3 traps for box alarms; POST …/log-export/test sends a test message. Delivery failures are audited (roost.log_export.delivery_failed) and alert the Owners. JSON line and MIB layout: Roost → Audit-log export.
  • Time policy. GET/PUT /api/org/{slug}/roost/time-policy (NTS/NTP servers, allowed drift, refuse-signing flag); GET …/{serial}/status gives the per-box verdict. Boxes read the policy from GET /api/roost/boxes/{serial}/policy and report with POST …/time-sync; drift over the limit is roost.time.drift_exceeded.
  • Device identity and attestation. GET /api/org/{slug}/roost/{serial}/identity (signed by Axowl, key at GET /api/roost/identity-public-key), nonce challenges (POST …/attestation/challenge) answered by the box with a device-signed POST /api/roost/boxes/{serial}/attestation; measurements are compared with the previous attestation and the published reference (GET /api/roost/reference-measurements).
  • Box requests are device-signed (X-Roost-Box, X-Roost-Timestamp, X-Roost-Signature); box alarms arrive at POST /api/roost/boxes/{serial}/alarms as roost.alert.raised.
  • New audit events RST050–RST059 (roost.auditor.*, roost.log_export.*, roost.time.*, roost.box.attestation_*).

2026-10-20 — Roost air-gapped files and usage

Section titled “2026-10-20 — Roost air-gapped files and usage”
  • New POST /api/org/{slug}/roost/airgap/challenge, POST …/airgap/import (bind entry = Owner seal; status and usage entries signed by the box), GET …/airgap/export?serial= (signed with the Roost identity key) and GET …/roost/usage.
  • Air-gapped boxes (licenseKind: AirGapped) are no longer shown gray for a missing report.
  • New audit events roost.airgap.imported, roost.airgap.exported. The marketplace key roost turns on by the device ledger (free; POST /integrations/roost/enable answers 409 ROOST_DERIVED).
  • Reference: Roost.
  • New POST /api/org/{slug}/roost/devices/{serial}/transfer (Owner seal, purpose roost.transfer) and GET /api/org/{slug}/roost/transfers. The Axowl operator approves or rejects; an approved box re-enrolls in the new organization.
  • New audit events roost.transfer.requested, roost.device.transferred, roost.transfer.rejected, roost.test.recorded, roost.sale.recorded.
  • Reference: Roost.

2026-10-20 — Roost maintenance cases, disposal certificates and firmware rollouts

Section titled “2026-10-20 — Roost maintenance cases, disposal certificates and firmware rollouts”
  • New POST /api/org/{slug}/roost/maintenance, …/maintenance/{id}/advance (approve = Owner seal), …/maintenance/{id}/dispose (Owner seal → signed disposal certificate, box Destroyed), GET …/maintenance/{id}/certificate, POST …/firmware/rollouts/{id}/rollback (Owner seal).
  • New box endpoints POST /api/roost/rollout-poll and POST /api/roost/rollout-status (attestation-key signed): pending rollouts and rollback requests; Downloaded → Staged → Verified → Committed / RolledBack.
  • New audit events roost.maintenance.opened, roost.disposal.certified, roost.firmware.released, roost.firmware.committed.
  • Reference: Roost.

2026-10-20 — Roost roles, quorum policy and the auditor view

Section titled “2026-10-20 — Roost roles, quorum policy and the auditor view”
  • New POST /api/org/{slug}/roost/roles, POST …/roles/{id}/revoke (Owner seal, purpose roost.role) for Security Officer / Operator / User. Auditors stay on the Owner-quorum endpoints.
  • New PUT /api/org/{slug}/roost/quorum/{purpose} (Owner seal, purpose roost.quorum.policy): physical keys, M-of-N seals by role, Axowl ticket per action.
  • New GET /api/org/{slug}/roost/audit-summary: Auditor-only once an Auditor exists — recent Roost audit chain with seal/signature presence, export and time-policy state, per-box drift.
  • New audit events roost.role.assigned, roost.role.revoked, roost.quorum.policy_changed.
  • Reference: Roost.

2026-10-19 — Roost status reports, time series and alerts

Section titled “2026-10-19 — Roost status reports, time series and alerts”
  • New anonymous POST /api/roost/report: a bound box sends its signed status once a minute (power, fans, voters, tamper mesh, batteries, temperatures, voltages, NTP, firmware slots). Replay and clock-skew protected.
  • New GET /api/org/{slug}/roost/devices/{serial}/telemetry?range=1h|24h|7d for the four graphs, and GET /api/org/{slug}/roost/alerts / PUT …/alerts/recipients.
  • Thresholds evaluated on every report: red immediately, yellow after 5 minutes, gray after 10 minutes without a report, one recovery notice. Delivered on the real-time feed (ReceiveRoostAlert), the notification inbox and email.
  • New audit events roost.alert.raised, roost.alert.cleared.
  • Reference: Roost.

2026-10-19 — Roost device ledger: binding, pairs, replacement, envelopes, enrollment (Owner-sealed writes)

Section titled “2026-10-19 — Roost device ledger: binding, pairs, replacement, envelopes, enrollment (Owner-sealed writes)”
  • New POST /api/org/{slug}/roost/bind/challenge and POST /api/org/{slug}/roost/bind: bind a delivered box with the product certificate number, the box’s attestation signature over a one-time number, and the Owner’s passkey seal (purpose roost.bind).
  • New POST /api/org/{slug}/roost/pairs, /pairs/{id}/replacement (six ordered steps), /pairs/{id}/envelopes, /envelopes/{id}/verify, /envelopes/{id}/revoke — all sealed with purpose roost.pair.
  • New POST /api/org/{slug}/roost/enrollments/{id}/advance: arrival check → measurement compare → quorum registration (sealed, purpose roost.enrollment).
  • New audit events roost.device.bound, roost.device.lifecycle_changed, roost.enrollment.completed, roost.pair.replacement_step, roost.envelope.issued, roost.envelope.revoked, roost.pair.registered.
  • Reference: Roost.

2026-10-19 — Roost device ledger: read API

Section titled “2026-10-19 — Roost device ledger: read API”
  • New read endpoints under GET /api/org/{slug}/roost/ — summary, devices, devices/{serial}, pairs, events, enrollments, roles, maintenance, firmware. Owner or Admin of the organization.
  • A device row carries the latest signed status of the box: power supplies, fans, the three voters and quorum, tamper mesh / lid / light / EFP, erase state, both batteries, temperatures, voltages, NTP drift, firmware slot A/B measurements and the sign counter.
  • Audit event names reserved for ledger writes (roost.device.bound … roost.test.recorded); they are emitted as each write endpoint lands.
  • Field reference: Roost.

2026-10-18 — Roost demo boxes: owner-sealed re-arm tickets

Section titled “2026-10-18 — Roost demo boxes: owner-sealed re-arm tickets”
  • New POST /api/org/{slug}/roost/rearm-ticket: the organization Owner seals (purpose roost.rearm, bound to the box ID and the one-time number the box shows) and receives a ticket the demo box verifies before it creates new keys.
  • New anonymous GET /api/roost/rearm-public-key: the ES256 public key and fingerprint that demo boxes pin.
  • New audit event roost.rearm.ticket_issued with the box ID, nonce, expiry, key fingerprint and seal proof.
  • Ticket format and flow: Roost.
Section titled “2026-10-16 — Approval texts need SMS consent; U.S. texts end with STOP”
  • Approval confirmation links by text now go only to a mobile number whose owner ticked the SMS consent box when requesting the verification code. Without consent the link goes by email, as before.
  • Texts to U.S. and Canadian (+1) numbers end with “Reply STOP to opt out.” Replying STOP stops further texts to that number; HELP returns help.
  • A free-text message that merely contains digits (a link token, a count) is no longer mistaken for a verification code. Only ”… verification code is 123456” is sent as a code.
  • The SMS terms are in the Terms and Privacy Policy.

2026-10-16 — Verification codes to U.S. numbers come by text, not a phone call

Section titled “2026-10-16 — Verification codes to U.S. numbers come by text, not a phone call”
  • U.S. and Canadian (+1) numbers now get verification codes by SMS like every other country on the SMS coverage list. The interim voice-call fallback (a call that read the code aloud) is gone.
  • End-user MFA responses (mfa/enroll/send, mfa/challenge/resend) and the login smsChannel field no longer return "voice"; the values are "sms" and "alimtalk".
  • Organization and contact OTP send (POST /api/org/otp/send, POST /api/v1/contact/otp/send) return { message } again, without the channel field. Error codes are unchanged.

2026-10-16 — Audit records say whether a human seal approved them

Section titled “2026-10-16 — Audit records say whether a human seal approved them”
  • Audit records produced by a request that passed a passkey step-up seal now carry businessData._humanSeal (credentialId, userId). Records written without a human seal (system work, sessions, API keys, agents) do not, and keep their previous shape.
  • The marker is inside businessData, so it is covered by payloadHash and the chain hash. No new query field or parameter. See Audit → Human seal marker.

2026-10-16 — Passkey changes: security email, activity log rows, org change history

Section titled “2026-10-16 — Passkey changes: security email, activity log rows, org change history”
  • A security email now goes to the account address when a passkey is added (api/passkey/add) or removed, with the key ID and the first 16 hex characters of the public key’s SHA-256 fingerprint. Binding a seal key ID (POST /api/v1/seals) does not send one.
  • GET /api/auth/activity-log returns seal.lifecycle.registered and seal.lifecycle.revoked rows next to logins, and every row has two new fields, keyId and keyFingerprint (null on non-passkey rows).
  • Passkey registrations are now also recorded on the platform account’s audit trail, as removals already were, so they appear in the account’s own activity log.
  • Org Owners and Admins see the same rows in the dashboard under Seal Registry ▸ Change history, read through the existing audit query with eventTypes=seal.lifecycle.registered,seal.lifecycle.revoked. See Seals → Passkey change history and alerts.

2026-10-15 — SMS verification codes: 76 countries, one list for OTP and MFA

Section titled “2026-10-15 — SMS verification codes: 76 countries, one list for OTP and MFA”
  • Phone verification codes go out by SMS to 76 countries, listed on the new SMS coverage page. A number outside the list is not sent to and fails with region_not_supported — previously end-user MFA tried every country and only failed if the carrier refused.
  • Countries are in three groups. Standard countries have no daily cap per number; Limited countries allow 2 codes per number per 24 hours and Restricted countries 1. Only delivered codes count.
  • Organization phone OTP and end-user MFA now read the same list and share the same per-number counters. Organization OTP reports region_daily_limit and region_fuse when a cap is hit; end-user MFA reports region_not_supported for all three cases, in the same 502 { error } shape as before.
  • Caribbean +1 area codes (Jamaica, the Dominican Republic, the Bahamas, Bermuda and others) are outside the list. Puerto Rico, the U.S. Virgin Islands and Guam are covered.

2026-10-13 — Agent MCP: retried write calls are answered once

Section titled “2026-10-13 — Agent MCP: retried write calls are answered once”
  • Every write tool on the agent MCP server (axowl_propose_code_change, the seven approval tools, axowl_ask, and the six axowl_pm_* write tools) now treats an identical call from the same badge within 24 hours as the same action and returns the first result again, prefixed (replayed — …). A runtime that retries a timed-out call no longer opens a second pull request, files a second approval, sends a second question email, or flips a checklist item back. A retry that arrives while the first call is still running is told to wait.
  • New optional idempotencyKey argument on those tools: name the action yourself to be recognised across rewordings, or pass a new key to repeat an identical action on purpose. Refusals and errors are never replayed. See MCP → Retries are safe.

2026-10-12 — @axowl/sdk 0.3.1: sign-in card recovers from a failed config load

Section titled “2026-10-12 — @axowl/sdk 0.3.1: sign-in card recovers from a failed config load”
  • @axowl/sdk 0.3.1 (React): when the sign-in card cannot load the app’s sign-in configuration (for example during an API restart) it no longer stays stuck on “Can’t load sign-in”. It retries three times (1.5 s, 3 s, 5 s) and then shows a Try again button; only a 404 (unknown app) is shown immediately. The card also states For your security, [the app’s name] does not use passwords. above the email button and is slightly more compact (404 px wide).
  • @axowl/sdk-core 0.3.1 and @axowl/sdk-backend 0.3.2: republished with no code change. All three npm packages are now published through npm trusted publishing (OIDC from GitHub Actions) instead of a long-lived token; nothing changes for consumers.

2026-10-10 — Sell through your own gateway: won prices, PortOne checkout, billing-key renewals

Section titled “2026-10-10 — Sell through your own gateway: won prices, PortOne checkout, billing-key renewals”
  • Products: POST /api/org/{slug}/app-groups/{groupId}/products accepts currency (USD | KRW); KRW needs a connected gateway and whole won. Product lists return currency.
  • Marketplace checkout returns provider: "portone" with an intent for the brand’s PortOne window instead of a Stripe redirectUrl when the seller uses its own gateway. New POST …/marketplace/apps/{groupKey}/own-gateway/billing-key confirms the registered card and charges the first period; renewals are charged by Axowl each period.
  • Connecting a gateway now also informs the marketplace ledger (502 GATEWAY_MIRROR_FAILED if it cannot be reached — nothing is connected then). See Applications → Own payment gateway.

2026-10-10 — Connect your own payment gateway (Korea first)

Section titled “2026-10-10 — Connect your own payment gateway (Korea first)”
  • New GET/POST /api/org/{slug}/app-groups/{groupId}/payment-gateway and POST …/payment-gateway/disconnect: a brand connects its own PortOne-backed gateway (KG Inicis, Toss Payments, NHN KCP and others). Owner-only, passkey seal, secrets stored encrypted and never returned, keys verified with PortOne before saving.
  • New per-brand webhook POST /api/v1/webhooks/portone/brand/{token}, verified with the brand’s own webhook secret and re-checked against PortOne before a payment is recorded.
  • New audit events appgroup.payment_gateway.connected / .disconnected. See Applications → Own payment gateway.

2026-10-10 — Payment choice defaults by country

Section titled “2026-10-10 — Payment choice defaults by country”
  • The app-group creation dialog now offers two payment options, Application payment integration and Connect my own payment gateway, and picks the default from the organization’s country. In the 44 countries where Stripe opens seller accounts the default is Application payment integration; elsewhere (for example India, Taiwan) that option is shown as not yet available and the default is your own gateway. In Korea only your own gateway is offered. No API change: monetizationEnabled is unchanged.

2026-10-09 — Choose the payment service when creating an app group

Section titled “2026-10-09 — Choose the payment service when creating an app group”
  • POST /api/org/{slug}/app-groups takes monetizationEnabled (default false). true creates the group with Application payment integration; it is owner-only and counts against the plan’s collecting-group limit, exactly like turning collection on with PUT. See Applications → App groups.

2026-10-06 — AlimTalk channel and template registration inside Axowl

Section titled “2026-10-06 — AlimTalk channel and template registration inside Axowl”
  • The Korean Package tab now links a KakaoTalk channel and requests template review without any external window: POST …/kakao/sender/request (verification code to the channel admin), POST …/kakao/sender/verify, DELETE …/kakao/sender, POST …/kakao/templates, DELETE …/kakao/templates/{code}, GET …/kakao/categories. PUT …/kakao now takes { templateMap } only.
  • Removed: POST …/kakao/member, POST …/kakao/popup/{kind}, POST …/korean-package/popbill-member, the channelId/senderNumber fields of PUT …/kakao, and the MEMBER_REQUIRED / UNKNOWN_CHANNEL / UNKNOWN_SENDER_NUMBER / POPBILL_REJECTED errors (vendor refusals are now 502 VENDOR_REJECTED).
  • The otp recommended template carries #{service} and #{code}; AlimTalk is sent without SMS fallback.
  • See KakaoTalk AlimTalk from your own channel.

Screenshots on a marketplace listing. New POST and DELETE /api/org/{slug}/app-groups/{groupId}/marketplace/screenshots add up to three screenshots or mockups to a listing; GET /api/public/marketplace/apps now carries screenshots and publisherSlug. Uploaded images (screenshots and the app-group icon) are identified from their bytes, and SVG files are cleaned before they are stored. See Applications → Marketplace.

Sales revenue by period, app and product. New GET /api/org/{slug}/marketplace/revenue for the selling organization, and GET …/app-groups/{groupId}/marketplace/sales for one app’s payments. See Billing & monetization → Reading what you collected.

Plan changes and add-ons after install. New GET …/marketplace/apps/{groupKey}/subscription and POST …/marketplace/apps/{groupKey}/change-plan (prorated, sealed like uninstalling). A OneTimeAddon can be bought while the app is installed; 409 ALREADY_INSTALLED now applies to Subscription plans only. The payment engine’s POST /api/public/v1/marketplace/purchases accepts event: past_due: the app stays installed and both organizations are notified. See Applications → Selling on the marketplace.

The seller is the merchant. Card payments that belong to a seller — marketplace apps, catalog products, voucher purchases and brand-credit top-ups — are now charged on the seller’s own Stripe account. Refunds, card disputes, the dispute fee and the card processing fee come out of the seller’s Stripe balance; Axowl receives the take rate and the marketplace commission only, and no longer collects an estimated card fee. Sellers with a payout account created before this date set up card payments once more (same screen); until then checkout answers 409 brand_not_payable with reason: "upgrade_required". See Billing & monetization → The seller is the merchant.

Billing page for marketplace buyers. New POST /api/org/{slug}/marketplace/apps/{groupKey}/billing-portal opens the card-payment provider’s billing page for an installed paid app: change the card, see receipts, cancel or take a cancellation back. Owner or admin of the buying organization, sealed like uninstalling. See Applications → Selling on the marketplace.


Marketplace listing needs Pro, and listings are translated. Publishing an app on the marketplace now requires the Pro plan or higher (403 UPGRADE_REQUIRED on Free; unpublishing is always allowed). New POST /api/org/{slug}/app-groups/{groupId}/marketplace/translate returns an AI translation draft in eight languages; the owner reviews it and sends it as translations with the sealed publish. GET /api/public/marketplace/apps adds i18n for reviewed listings.

Marketplace lists across your organizations. GET /api/me/marketplace/apps returns the app groups you can publish, and GET /api/me/marketplace/subscriptions returns what each organization turned on or installed. Both cover the organizations where you are an owner or admin. Publishing itself is unchanged.

See Applications → Marketplace.

.NET Axowl.Sdk.Identity.Client 0.2.2 — three fixes, upgrade from 0.2.1.

  • The default Issuer is now the value Axowl actually writes, {Authority}/api/public/orgs/{OrganizationSlug}. It used to be "axowl", so a setup that followed the README rejected every real token with 401. Issuer can still be set explicitly; the 401 description now shows the token’s issuer instead of '(null)'.
  • The permissions claim (a JSON-encoded string array) is parsed into a list. Before, GetAxowlPrincipal().Permissions held the raw string as one item and every [RequirePermission] / HasPermission check failed with 403. Several plain permissions claims are still accepted.
  • [RequirePermission] is enforced on minimal-API endpoints: 401 without a token, 403 without the permission. It was an MVC-only filter, so on app.MapGet(..., [RequirePermission("x")] ...) it did nothing. A .RequirePermission("x") endpoint convention was added. Controllers behave as before.

See Identity SDK.

Python axowl-sdk 0.1.1. The SDK now sends its own User-Agent (axowl-sdk-python/<version>) when it downloads the signing keys and calls the identity API. 0.1.0 used Python’s default agent, which the API edge blocks, so every token failed with reason="jwks". Upgrade from 0.1.0. See Python SDK.

Java com.axowl:axowl-sdk 0.1.1. The signing-key download waits up to 5 seconds to connect and to read (it was 0.5 seconds), so a slow network no longer causes occasional jwks rejections. See Java SDK.

LINE login no longer needs the Korean Package. LINE is a Japanese service, so it is now a standard provider like Google, Apple and GitHub: an app group can enable it on any plan without the add-on, it stays in GET /api/public/apps/{applicationKey}/login-methods, and …/auth/social/line/start and POST …/auth/social with Line no longer return 402 ADDON_REQUIRED. Kakao and Naver still require the Korean Package. GET /api/org/{slug}/korean-package no longer returns lineLogin per app group. See Social login.

End-user event retention. The hot end-user event store now keeps 181 days. Monthly active users (billing, organization screens, the six-month trend) are counted from a per-day activity table, so they are unaffected. See Audit.

Audit file downloads are Owner-only. GET /api/v1/admin/audit/presign for raw or compacted log files and the new GET /api/v1/admin/audit/compact/urls require the organization Owner with the unconstrained list scope, on Pro or higher. A file holds every event of the period, including owner-only payment.* events, so the row filtering that /query applies to Admins cannot apply to a file.

Forensic search reaches the archive. GET /api/v1/admin/audit/stats/forensic fills days that are no longer in the hot end-user store from the end-user archive.

Managed database limits. Strix databases are created, and re-checked daily, with idle suspend after 5 minutes, a 0.25–1 CU compute range (Always-On: 0.25 CU, no suspend), and 1 day of change history. See Database management.

SDK timeouts. JavaScript requestTimeoutMs (default 15 s) and .NET CallTimeout (default 10 s) on every client. See SDK overview.

Schema changes go to staging first. A database connection now declares what it is — PATCH /api/org/{slug}/app-groups/{groupId}/data-sources/{id} takes purpose (production or staging), and the console’s Database tab shows it. axowl_request_migration takes target, and defaults to staging when the app group has one. Before applying to production, Axowl checks that the exact same SQL already ran on staging (by hash, in axowl_applied_migration) and refuses if it did not — a stamp does not override that. If staging cannot be reached, the change still applies and the receipt says the check could not be made.

Guards: the primary connection cannot be marked staging, and an app group has at most one staging database. Discarding a story’s work (Redo / Back to backlog) does not wipe staging — Axowl notes on the card what that attempt left behind, and reversing it is its own approval.

A backend staging workflow template. backend-staging builds a .NET backend and pushes it to the repository’s own GitHub Packages as a staging image — no extra secrets (it uses GITHUB_TOKEN), one staging deployment that stories take turns using. Set the repository variable STAGING_URL to the staging address. The deploy job never runs the repository’s own Dockerfile.


Deploys go out on the customer’s own Cloudflare account. A person connects it per client — GET/POST/DELETE /api/org/{slug}/applications/{appId}/cloudflare (account id + a Pages:Edit API token, checked against Cloudflare before it is saved, stored encrypted, never returned by any endpoint). Until that connection exists, publishing, project verification and project creation are refused: Axowl has no fallback to its own Cloudflare account, and the deploy service no longer reads a server token at all.

Agents can ask for the Pages project itself with axowl_request_pages_project(projectName, reason) — an approval (pages.project.create) carrying the name only. Which account it is created on comes from the client’s connection, never from the agent or the approval document. The console’s repository tab now shows both cards: Cloudflare connection, then deployment target.


A story in testing carries the address of the thing it built. When a story moves to Testing, Axowl reads the GitHub Deployment your deploy workflow made for that branch (environment: preview) and attaches its address to the card, so the person testing opens the running result instead of a diff. Agents cannot write it — axowl_pm_link refuses a preview kind — because the point of the address is that it is the thing that was actually deployed. Repositories without an Axowl deploy workflow are unaffected and are tested from the pull request.

Previews do not outlive their story: Redo, Back to backlog and a completed merge delete the agent branch (axowl/agent/…), which ends its branch deployment. Request revision does not — that story continues on the same branch and pull request. See One story at a time.


Projects have a documents tab, and agents can read it. GET/POST /api/org/{slug}/projects/{projectId}/docs, GET …/docs/{fileId}/download, DELETE …/docs/{fileId} — files live in the file box’s project scope, gated by project assignment. Agents read them with axowl_pm_files / axowl_pm_file (text only, 512KB) and cannot write or delete. See Project documents.

Agents can ask for a schema change, and see which repository secrets are set. axowl_request_migration(sql, reason) files an approval carrying the SQL in full; once stamped, Axowl applies it to the app group’s database in one transaction and records it in axowl_applied_migration (approval op db.migration.apply). axowl_repo_secrets and GET …/applications/{appId}/repository/secrets report which secrets the deployment workflow needs and which are set — names only: values are entered by the customer in their own GitHub and never pass through Axowl. See Schema changes.

Agents can ask for a deployment workflow, and read why CI failed. axowl_request_workflow files an approval carrying the complete workflow file; once a person stamps it, Axowl commits it to the repository’s default branch (approval op workflow.install). Templates only — the approval carries a template id and its values, never YAML, and .github/ remains closed to agents. axowl_repo_ci_log returns the trailing lines of the failed job’s log for one pull request. See Deployment workflows.

Project stories go one at a time: write, test, deploy, done. Story state gains Deploying (between InReview and Done; the board shows InReview as Testing). Two new routes: POST /api/org/{slug}/projects/{projectId}/stories/{storyId}/start opens a ToDo story for its agent, and POST …/stories/{storyId}/review takes decision (pass · revise · redo · hold) and note. Agents move a story to InReview only with a testGuide, an attached pull request and green CI; they can no longer move stories to Done, which now follows the merge. agent.wake gains the reasons story_start, story_passed, story_revise, story_redo, story_hold and setup_done. axowl_pm_create_story takes forPerson for setup a person must do. See One story at a time.

M2M tokens can outlive 5 minutes — per key, for devices. POST /api/m2m/token accepts lifetimeSeconds; the issued lifetime is min(request, key ceiling, 7 days) and expiresIn reports the real value. The ceiling is set per key at issue time (maxTokenLifetimeSeconds on POST /api/org/{slug}/settings/keys/api-keys, or the new lifetime picker in Settings → Keys). Anything above the default requires the organization owner and their personal seal; omitting lifetimeSeconds still yields 300 on every key, so existing integrations are unchanged. Revoking a key keeps refusing its tokens for the key’s whole ceiling. See Longer-lived tokens for devices.

Strix credit gate accepts a Korean recurring-billing card. POST …/data-sources/strix, POST …/data-sources/{id}/reconnect and POST …/data-sources/{id}/tier no longer return 402 CREDIT_REQUIRED when the paying organization has an active Korean billing card registered (POST /api/org/{slug}/billing/krw/billing-key/issue → confirm). A registered card counts the same as auto top-up: month-end usage is charged to it. The 402 body is unchanged (code, balance, required, reserve). See Database management.

Korean Package: AlimTalk from your own KakaoTalk channel. An app group on an organization with the Korean Package can link its own Kakao business channel; verification codes for Korean phone numbers then go out as AlimTalk under that channel, with SMS as the automatic fallback. Setup is in the app group’s new Korean Package tab, and the underlying routes are documented in KakaoTalk AlimTalk from your own channel.

  • GET | PUT /api/org/{slug}/app-groups/{groupId}/kakao, POST …/kakao/member, POST …/kakao/popup/{channel|template|sender} are new (dashboard session, app-group scopes).
  • GET /api/org/{slug}/billing/business-lookup?number= is new: verifies a Korean business registration number (checksum, then Popbill business-info lookup) and returns the company name, representative, address and business type for autofill. 30 lookups per organization per day.
  • The app group’s Korean Package tab has a two-step setup wizard (channel → template). Business verification and the Popbill link moved to the organization-level Korean Package hub (2026-09-07), under Applications → Overview, which is also where the package is turned on or off and where each app’s Korean sign-in providers and settlement currency are switched.
  • GET /api/org/{slug}/korean-package (2026-09-07) is new: one read of the package state, the organization’s business registration and Popbill link, and every app group’s Korean switches. POST …/korean-package/popbill-member links the organization with Popbill (billing scope).
  • GET /api/org/{slug}/billing/address-search?q= is new (2026-09-07): Korean road-name address search via the Ministry of the Interior and Safety API, server-side, 300 per organization per day. The wizard’s business-address field is now search → pick → detail.
  • Audit: appgroup.kakao.channel_linked / channel_changed / channel_unlinked / member_joined.

Board quorum now counts who attended, not who was invited. Creating a board meeting puts every director on the participant list automatically, and quorum was counting that list — so “present” meant “on the board”, one absent director left an unfillable gap, and items that a real board had carried were recorded as NotVoted. Quorum is now the directors marked present.

  • POST /api/org/{slug}/meetings/{meetingId}/participants/{participantId}/attendance is new. Body { present: true } (send false to undo). The organizer or the chairman may call it; it emits business.meeting.attendance_marked. Participant payloads already carried checkedInAt — it is now the value quorum is computed from.
  • Casting a vote marks that director present automatically, so an existing meeting keeps the attendance its votes already prove. Votes cast before this change were backfilled the same way.
  • POST /{meetingId}/agenda-votes now requires the director to be a participant of that meeting — 403 { error: "NOT_A_PARTICIPANT" }. Only the votes of currently seated, present directors are counted, so a vote left behind by a removed director no longer inflates a tally.
  • close-preview and complete report the same numbers as before; what changed is the definition behind attendingCount.

A meeting can no longer end up with nobody able to close it. POST /api/org/{slug}/meetings always gives the organizer a participant row now (previously only if they listed themselves), and the organizer-only actions — compile, close-preview, complete, delete — also accept the meeting’s own OrganizerId, which unblocks meetings already in that state.

The agenda is no longer frozen at creation. Items could only be created with the meeting, so a typo could not be fixed and an item raised in the room had nowhere to go.

  • POST /api/org/{slug}/meetings/{meetingId}/agenda-items adds one. Body takes title and an optional memo. It is numbered after the highest number the meeting has ever used, deleted ones included — numbers never move, because votes point at them.
  • PUT .../agenda-items/{index} now also accepts Title, and DELETE .../agenda-items/{index} drops an item (soft).
  • Both refuse an item that already has votes — 400 { error: "AGENDA_HAS_VOTES" }. Each vote stores the wording it was cast on, so rewriting or removing the item would break what a director agreed to. Deleting leaves a gap in the numbering, and that gap is intentional: renumbering would move existing votes onto a different item. There is no reorder endpoint.
  • Any of the three rebuilds the meeting’s Agenda summary and emits business.meeting.updated.

A meeting that never happened can be called off. Nothing could set Cancelled, and delete only accepts Completed or Cancelled — so a meeting scheduled by mistake stayed on the list forever. POST /api/org/{slug}/meetings/{meetingId}/cancel takes { "reason": "..." } (required), sets Cancelled, and emits business.meeting.cancelled. It refuses a meeting that has votes — 400 { error: "MEETING_HAS_VOTES" }: a vote means the meeting sat and decided, which is a close, not a cancellation.

Follow-up meetings go through the same doors as any meeting. POST /{meetingId}/complete with CreateFollowUp used to build the meeting directly, bypassing the plan gate, the board add-on quota, and the board/chairman requirements, and emitting no business.meeting.created. It now runs the ordinary create path — the follow-up is a new meeting and counts as one. When a gate blocks it, the close still succeeds and the response carries followUpError instead of failing.


End-user MFA no longer reports a code as sent when it wasn’t. POST /apps/{key}/auth/mfa/enroll/send swallowed every SMS failure and returned { sent: true } regardless, and a login that returned mode: "challenge" did the same — so a user whose code never left the carrier saw a code-entry screen forever, with no way back. Three changes:

  • POST /apps/{key}/auth/mfa/enroll/send now returns the failure: 502 { error } where error is invalid_number, region_not_supported, or send_failed (503 { error: "send_failed" } when no SMS provider is configured). Same error vocabulary as the organization OTP endpoints. 200 { sent: true } still means the message was accepted by the carrier.
  • A login that needs MFA returns two new fields alongside the existing { mfaRequired, mode, mfaToken }: smsDelivered (true/false, and null in enroll mode where nothing is sent yet) and smsError (the same error code, or null). Existing fields are unchanged — if you ignore the new ones, nothing about your integration changes.
  • POST /apps/{key}/auth/mfa/challenge/resend is new. Body { mfaToken }. It issues a fresh code to the phone already on the session and returns { sent: true }, or the same error shape as above. Use it when smsDelivered is false, or when the user asks to resend. One send per 60 seconds per session — 429 { error: "cooldown" } in between; a failed send does not start the cooldown. mfaToken is only issued after the first factor has already passed, so this endpoint cannot be used to send messages to an arbitrary number.

Projects can be deleted without archiving first, and a roadmap is one call instead of N.

  • DELETE /api/org/{slug}/projects/{id} takes a new ?purge=true. Without it the route behaves as before — it discards an empty shell (Planning, no stories, no epics) and refuses anything else with 409 PROJECT_NOT_EMPTY_SHELL. With it, the project’s rows in the axowl_pm_* tables of your database are deleted too (seven tables, children first, one transaction) and the response reports { discarded, purged, purgedRows, purgedFiles }. It fails closed: if your database cannot be reached nothing is deleted at all (409 PM_DATASOURCE_UNREACHABLE), because removing our row while yours survives leaves orphaned stories in your database forever. Attachment files are not erased — the ledger rows are soft-deleted and the objects go to the retention worker, as everywhere else in Axowl. An archived project is refused (409 PROJECT_ARCHIVED); restore it first.
  • ?purge=true now requires a personal seal and the project’s name. The scope (org.project.create) is unchanged, but on its own it made whoever may start a project also the person who may burn one — a weaker door than archiving, which keeps a copy. The purge branch now takes a body: confirmName must match the project name exactly (400 NAME_MISMATCH) and assertionResponseJson must be a passkey assertion bound to that project and the purge action (401 SEAL_REQUIRED), the same ceremony as deleting a data source for good. The empty-shell branch is unchanged — no body, no seal, because there is no work to lose.
  • GET /api/org/{slug}/projects/roadmap is new: every visible project with its epics, story counts and a server-computed progress, in one response. Epics are read once per app group rather than once per project. Archived projects are excluded, scoped guests get only their invited projects, and a database that cannot be read fails per project — that row carries an error (PM_DATASOURCE_REQUIRED / PM_SCHEMA_FAILED / PM_DATASOURCE_UNREACHABLE) and an empty epics array while the rest still return theirs.

See Projects.

A suspended workspace now says why — by email, to its owners. When Axowl suspends a workspace, every sign-in through your apps returns 403 with errorCode: "ORG_SUSPENDED", unchanged. What is new is that the owners of that workspace now receive an email at the moment of the suspension naming the reason (unpaid balance, terms of service, a security investigation, or their own request) and what clears it. Until now nothing was sent, so the first sign of a suspension was your users failing to log in. When the suspension is for an unpaid balance and the bill is paid by another organization, that payer’s owners are written to as well — they are the ones who can settle it. The 403 body itself is still the generic notice: reasons go to the workspace’s owners, never to the end users of your apps.

Two more letters follow the same rule. If we correct the reason while a workspace is still suspended, the owners get a second email marked as an update to the first — the suspension itself is untouched, so how long it has been suspended does not reset. And when the suspension is lifted, the owners are told that too, so nobody is left assuming they are still locked out. Sign-ins may still be refused for up to five minutes after a workspace comes back: the entry gate caches its decision, and the email says so.

Board meetings run off an agenda, and each item is voted on separately. /api/org/{slug}/meetings gained the agenda surface: GET|PUT /{id}/agenda-items/{index}, POST /{index}/open and /close to bracket when voting is possible, PUT /{index}/recording, GET|POST /{id}/agenda-votes, GET /{id}/close-preview, POST /{id}/complete and POST /{id}/minutes/compile. A vote is one director, one vote — not weighted by shareholding — and it is confirmed with the director’s personal seal bound to that item. An item resolves to one of four values, and NoQuorum is not Rejected: a resolution that never became capable of being taken is a different fact from one the board turned down.

Board meetings are now Pro-or-higher, with the first one free per organization. From the second, POST /api/org/{slug}/meetings answers 402 with an ADDON_REQUIRED body naming the add-on key addon.board_meeting plus freePerOrg, everCreated and priceUsd. It is deliberately not UPGRADE_REQUIRED — raising the plan does not open this door, so the error names the add-on that does. The count is per organization for its lifetime and deleting a meeting does not give it back. The corporation / SPV entity gate still sits in front: an entity without a board has no board meetings at any price.

See Meetings.

Project Management AI (addon.pm_ai) — the AI plans a project and then works it. Priced per connected app group, and it gates only the AI layer; the manual boards, epics and backlog stay on the plan axis.

  • POST /api/org/{slug}/projects/ai/draft turns a summary plus reference images (uploads and/or Figma frames) into proposed epics, stories with sub-tasks, and a roadmap layout. It returns 402 PM_AI_REQUIRED with a connectPath when the app group is not connected. The draft is a proposal only — nothing is stored until a human confirms, and the wizard then writes it through the ordinary POST /projects, /epics and /stories routes, so every gate on those still applies.
  • Six axowl_pm_* MCP tools let an assigned agent read and move its own stories. They carry no scope of their own: being assigned the story is what opens them.
  • agent.wake gained two reasons — story_assigned when a story gains an AI owner, and story_state_changed when an assigned story moves (except for whoever moved it). The webhook carries no story title or state; the agent reads what changed through its own tools.

See Projects and MCP.

Disconnecting a data source never deleted anything — now there is a way back, and a way to really delete. DELETE …/data-sources/{id} only flips isActive to false; the row, and for Strix the provider project (and its metering), stayed. But the row vanished from GET /, so it read as a deletion with no undo. Three changes on /api/org/{slug}/app-groups/{groupId}/data-sources:

  • GET /?includeInactive=true returns disconnected rows too; every row now carries isActive. The default (active only) is unchanged.
  • POST /{id}/reconnect restores a disconnected row in place — same record, so the history connected → disconnected → reconnected stays on one row. It re-tests the stored credentials and returns { reconnected, connected, error }. Emits appgroup.datasource.reconnected.
  • POST /{id}/delete deletes for good. Body { assertionResponseJson, confirmName }: a personal seal bound to this source and the delete action, and the connection’s exact name. Only a disconnected row is accepted (400 STILL_ACTIVE). For Strix the provider project is destroyed first — all data, no undo, no backup kept by Axowl — and the row is soft-deleted only after that succeeds (502 PROVIDER_DELETE_FAILED keeps the row for a retry). External connections lose only Axowl’s record. Emits appgroup.datasource.deleted with pd = provider delete confirmed.

See Database management.

Strix credit gate and non-payment enforcement. POST …/data-sources/strix and POST …/data-sources/{id}/reconnect (managed rows) now return 402 { code: "CREDIT_REQUIRED", balance, required } unless the paying organization holds at least $5 of Axowl Credit or has auto top-up on. When a monthly charge stays unpaid past the 3-day grace period, the organization’s Strix databases are suspended (disconnected, password rotated — data kept) and deleted 30 days later unless reconnected; owners and each database’s creator are warned by notification and email at suspension, 7 days before, and 1 day before deletion. Details in Database management → Credit gate and non-payment.


MCP — agents file approvals. Four tools on /mcp: axowl_approval_forms, axowl_request_permission, axowl_approval_submit, axowl_approval_status. An agent refused a scope can ask for it: the Access Approval form that grants it is submitted under the agent’s badge, a person stamps it in the dashboard, and the scope lands on the badge. Agents never stamp, and a machine requester is never auto-approved — see MCP server.

Hiring an agent gives it a manager. POST …/agents now adds a reporting edge from the badge to the person who hired it (existing agents were back-filled), and brings the shared AGENT_{KIND} role up to the kind template on every hire — previously a role created before a template change kept the old scope set.

Every agent kind carries the employee scopes (org.work_item.list/read, org.reporting_instance.create/read/list). test included.

Fixed: a dashboard-issued MCP credential (ah_live_…) was refused as credential not recognised — hash case mismatch between issue and resolve. Fixed: editing a form in the dashboard cleared its linked role, turning an Access Approval into one that granted nothing; PUT /approvals/work-items/{id} now leaves linkedRoleId untouched when omitted.


POST /api/org/{slug}/app-groups/{groupId}/data-sources/{id}/credentials — read back a connection’s credentials. Provisioning a Strix database handed you a password exactly once, in the create response, and nothing could show it again — so a database you owned could become one you had no way to reach. This route returns the host, user, password and two ready-to-paste forms (connectionString for ADO.NET, connectionUri for psql and most ORMs). It works for external connections too.

It requires a personal seal, bound to this specific data source and to the reveal action, so a seal taken for one database cannot be replayed against another. Every successful call emits appgroup.datasource.credential_revealed — this is the only path by which a stored password leaves the system in plaintext, and the record exists so that question has an answer later.

POST …/data-sources/{id}/rotate-password — reissue a Strix password. Reissues the managed role’s password and stores the new one. Same seal requirement, bound to rotate rather than reveal. The previous connection string stops working immediately — anything using it has to be updated with the value this call returns. Managed databases only; external connections return NOT_MANAGED, because their credentials are not ours to change. Emits appgroup.datasource.password_rotated.

Strix usage is now readable — GET /api/org/{slug}/settings/usage and the data-source listing. Consumption has been metered and billed since 2026-08-05, but nothing exposed it, so the first time you learned what a database cost was the invoice. The org-level route now returns strixGbMonths, strixCuHours, the amounts they come to, and the published rates; the data-source listing carries the same figures per database, so “which one is costing this” has an answer.

Two things the response is careful about. strixMinimumDueUsd is the gap to the monthly minimum, not a surcharge — it falls to zero once usage passes the floor. And strixMetered / usageMetered distinguish “measured, and it is zero” from “not measured yet”: consumption is read from the provider every few hours, so a database created moments ago legitimately reads zero. Figures are the current month in progress, while billing settles the previous month.

PATCH …/data-sources/{id} — edit a connection instead of replacing it. Until now the only way to change a password or a host was to disconnect and register again, which broke the record of how long that connection had been in place. Sends only what changes; an absent or empty password means unchanged, not cleared. Connection fields are editable on external connections only — for Strix, host and user are ours to keep correct, so only name applies. Connection-affecting edits are tested before the response returns, and the call emits appgroup.datasource.updated carrying the names of the changed fields, never their values. See Database management.


GET /api/v1/admin/audit/behavior/rollup — visitor behavior aggregates. The behavior beacon shipped with somewhere to send events but nowhere to read them back. This route closes that: pages, dwell time, referrers, campaign sources and countries for an app group, from sealed daily rollups. Takes orgId, days (1–90) and an optional appGroupId.

There is no add-on gate — the full 90-day window is free. Behavior analytics is metered on collection, not on looking at it, which makes it a different axis from Audit Analytics and its 3-day preview clamp.

Two things the response says out loud rather than papering over. Visitor counts are named visitorsUpperBound / identifiedUsersUpperBound because rollup rows are keyed by page, referrer and country — one visitor reading three pages is counted once per page. Views and dwell time are exact. And days without a rollup are omitted rather than drawn as zero, with coveredDates versus requestedDates telling you how much of the window is real.

Each daily rollup emits a sealed anchor (audit.rollup.behavior_created) carrying the file’s SHA-256 and a hash of its source list. Individual page views are not sealed — at that volume it would be neither affordable nor useful — so the integrity claim is made once a day at the aggregate: this count was computed over rows that have not been altered.


You choose what a session clash does — prompt on the owner gate. Handing someone over with login_hint used to have exactly one behaviour when the browser held a different Axowl session: stop, explain the clash, and make them pick. That screen is a wall for the common case, because the hint came from your own session — you already know who they are. The gate now takes prompt, in OIDC’s grammar: omitted (or login) honours the hint — Axowl signs that browser out of the other account and opens the form as the hinted address, saying on the card which session it ended — and select_account keeps the old chooser, signing nothing out until the person picks. An unrecognised value is treated as the default: a hint is an explicit instruction, and dropping it because a parameter did not parse is the worse failure. Axowl.Sdk.Identity.Abstractions carries the type as OwnerGatePrompt with AxowlOwnerGate.BuildLoginUrl(...), so the parameter names live in one place. Existing links keep working; they move from “ask” to “honour the hint” unless you send select_account. See The account-owner gate.


Seal who acted — actor_connected_id on the seal API. A seal used to record what happened but had no place for who did it: the SDK authenticated with an org API key, so a customer’s action sealed by your backend left no actor on the anchor. SealRequestProto (and the REST transcoding shape) now takes an optional actor_connected_id plus an actor_is_employee claim; the .NET SDK exposes it as SealActor on SealNewAsync / ReSealAsync. The actor is always a ConnectedId — end customers hold badges too — the server verifies the badge belongs to your organization and that the employee claim matches its subject axis, and the claim joins the hashed input so it cannot be swapped after the fact. Requests without an actor are unchanged, byte-for-byte. This closes the identity half of billing your own customers on Axowl: identity from introspect, attribution in the seal. See Integrity SDK.


IEntitySealer — edit a sealed record without breaking its seal. Sealing used to be a one-shot call at creation: the SDK gave you SealAsync and left the chain bookkeeping to you, so a record could be sealed but never legitimately changed. Editing a field inside the sealed payload made the row read as tampered, and the only alternative was to keep the interesting fields out of the seal entirely. IEntitySealer closes that: implement ISealedRecord on the entity, call SealNewAsync on create and ReSealAsync after an edit, and each version claims the next chain position — so Axowl holds one anchor per version and the edit is recorded, rather than hidden. Registered automatically by AddAxowlIntegrityClient(...), along with a default Sha256Hasher. Note the chaining rule it enforces for you: position n links to the sealedHash the server returned at n − 1, not to your own recordHash. See Integrity SDK.

Unpaid subscriptions now downgrade instead of running on indefinitely. A failed plan charge used to produce one low-balance notification and nothing else, so a plan could stay active without ever being paid for. A shortfall now opens a 3-day grace period — owners get an in-app notification and an email naming the exact deadline — and the plan drops to Free if it is still unpaid when the grace ends. Topping up at any point clears the clock, and a later shortfall starts a fresh one. Free organizations have nothing to downgrade and Enterprise agreements are settled contractually, so both are exempt; add-ons are not disabled by a downgrade. Your data is never touched. See Invoices.


POST /api/org/{slug}/app-groups/{groupId}/data-sources/strix — provision a managed database. Creates a Strix (sealed Postgres) instance and registers it as a managed data source, sealed from the first row. Body: { name, region? }, where region falls back to the organization’s home region. Requires the Strix integration to be active. See Database management.

Data source listings now carry Region and ProviderRegion. Region is two axes — where the organization is homed, and where a given provider actually runs the instance — so a listing tells you both rather than implying they are the same. See Database management.


GET /api/v1/admin/audit/analytics and /analytics/rollup — aggregated audit analytics. Usage aggregates, plus sign-in outcomes and event volumes read from sealed daily rollups. Rollups are built once a day just after midnight UTC and backfilled from archived raw events, so coverage reaches as far back as your raw history — not just from the day you enabled it. Both routes work without the Audit Analytics add-on over a 3-day preview window: the server clamps days to 3 and says so with "addonActive": false, "freeWindowDays": 3. The add-on opens the full 90 days. See Audit.

POST /api/org/{slug}/endusers/bulk-import — up to 500 end users per call. Body { appGroupId, users: [{ email, displayName? }] }. Each row goes through the same creation path as a single create — created Pending with an org identity, granted app access, sent a Magic Link invite in the background. Returns per-row created / duplicate / invalid. See End users.

enduser.security.login_failed — new event. Emitted on every rejected end-user sign-in. If you subscribe to event patterns on an outbound webhook, this one is new traffic. See Events.

GET …/data-sources/{id}/coverage — integrity coverage for a connected database. Reports hashes and row counts for a database you registered read-only. Contents stay yours — the check reads counts and hashes, not values. See Database management.

Withdrawals now disclose the cost-recovery deduction. Withdrawing prepaid cash returns it as a refund to the original card payment, minus 2.9% + $0.30 — the processing fee the payment processor does not return on refunds, recovered at cost with no margin — plus a proportional clawback of any volume bonus that rode in with that top-up. Both appear as their own ledger lines. See Invoices.


Outbound webhooks — Axowl can now call you. Register HTTPS endpoints under /api/org/{slug}/webhook-endpoints, subscribe them to event patterns, and receive signed deliveries. Signatures follow Standard Webhooks, so an existing Stripe/Svix verification library works unchanged. See Webhooks.

POST /api/public/v1/orgs/owner-status — is this person an Axowl account owner? Answers no_owner_account / owner_account_exists plus the organizations they own. The e-mail is read from the Bearer end-user token, not the request body — a body parameter would let any ah_live_ holder probe which e-mails have accounts. See Owner gate.

Fixed — ready was always false on organizations. The flag that tells you an organization can actually sign was reading a column nothing ever wrote, so every organization looked unusable. It now derives from the owner’s real credential. See Public API.

Fixed — /openapi/v1.json returned 500. The machine-readable schema was unavailable because a value-type default broke schema export. The document now generates.


.NET SDK — IAxowlAuth.RequireSignInAsync(returnUrl). The “this page needs a signed-in user” primitive: redirects once, guards against redirect loops, and returns to where the user was. Previously every consumer hand-rolled this. See .NET auth SDK.


End-user MFA (SMS) for your app’s users. An app group can require a second factor for its end users: POST /apps/{key}/auth/mfa/enroll/send and /mfa/verify. Existing login routes are unchanged — a login that needs MFA returns { mfaRequired, mode, mfaToken } instead of a session, and you exchange the token after verifying.


Full OIDC / OAuth 2.0 provider. /authorize, /token, /userinfo, /end-session with Authorization Code + PKCE. A standard OIDC client can now use Axowl as its identity provider with Authority alone — no Axowl SDK. Implicit and hybrid flows are intentionally not supported. See OIDC.


End-user tokens are RS256, verified through JWKS. Tokens were HS256, which meant verifying one required holding an Axowl secret — that is not a service. Tokens are now signed per organization with RS256 and published at /api/public/orgs/{slug}/.well-known/jwks.json. No shared secret.

OIDC discovery. /api/public/orgs/{slug}/.well-known/openid-configuration, with the token iss changed to the per-org URL so Authority auto-wiring works. In ASP.NET Core the whole integration becomes o.Authority = "https://…/api/public/orgs/{slug}".

Per-application CORS origins are enforced. The dashboard’s Allowed Origins field previously saved but did nothing. Browser origins you register are now honored, so cross-origin token exchange works without an Axowl code change.


Earlier changes predate this changelog. If you need the history of a specific endpoint, the reference pages cite the source file and line for every claim — that is the authoritative record.