#API architecture
Phase 8 latest (2026-10-14): Step 0 pushed (2899bd1); 8.5 Connect an existing OSS panel + server list/view built and pushed (Pair 2 API62–64, up to 5be6dec; response field system_info (was facts); tested live on an OSS test server; API address (https://api.…) + token only — panel web address refused with oss_not_a_panel). Next: 8.4 → 8.1 → 8.3. Connect a panel.
#Shared frontend API client (lib/api/)
| Concern | Rule |
|---|---|
| Base URL | https://sa-central-api-2.23-172-120-38.nip.io + /api/v1 (from env, never another pair's API). Known since 2026-10-08, when Phase 1 was built: routes live in routes/v1/<domain>.php under /api/v1 V8 requirement |
| Authentication | Authorization: Bearer <access token> added server-side (BFF route handlers / server components) from the httpOnly cookie. The browser never sees the token (D-1) |
| Cookies / session | httpOnly, secure, sameSite=Lax. Refresh token in its own httpOnly cookie |
| Organization context | Organization id in the path of organization-scoped calls (/organizations/{org}/…, V7 style) Assumption. Never a global header the server trusts blindly |
| Locale | Accept-Language from the chosen locale (cookie) |
| Errors | Normalised to {status, code, message, fields, reference}. 401 → refresh once → login. 403 → permission state. 404 → not-found. 422 → form. 429 → back off with Retry-After. The real shapes are now published (2026-10-09) — see the table below |
| Retry | GET only: 1 retry on network error/502/503/504 with backoff. Never auto-retry POST/PUT/PATCH/DELETE |
| Cancellation | AbortController per request. Cancel on unmount, org switch, and a newer search |
| Request states | idle → loading → success / error, exposed by a small hook or TanStack Query Open question (TanStack Query isn't installed; the project rules mention "TanStack queries") |
| Timeouts | 15 s default. Proxied server calls up to 60 s; long jobs use 202 + polling |
| Versioning | The API is versioned — /api/v1 V8 requirement. So keep the version in one place (the env base URL or a single constant), never repeated per call, and treat a future /api/v2 as a config change rather than a refactor. Worth knowing now: nothing in the doc says how long v1 is supported, so do not build anything that depends on the version string being permanent |
#The backend's own API reference — read it before building a screen V8 requirement
Since 2026-10-09 the backend publishes a live API reference, separate from the project doc:
| Page | https://sa-central-api-2.23-172-120-38.nip.io/v8-central-docs-ea60c2f1c31c3c9d/api-reference.html (re-renders itself every 30 s) |
| Covers | Only the endpoints Pair 2 has actually built, and it grows as they build. Today: the profile half (3.1–3.7), the whole security half (3.8–3.13, 3.16) and in-app notifications (3.14) |
| Does not cover | Authentication — that is Pair 1's code, and this reference says so. So it is not one place for the whole API yet |
| Remaining Account work | B3.15 channels now built, reference §§33–38; B3.17 server alerts wait for Servers |
This changes how a screen should be planned. Until now every field list on this site came from the project doc's one-line descriptions, so our rules were careful guesses. The reference gives exact field names, validation limits, response shapes, messages and status codes — so before building any screen, read the matching section there and use its names verbatim. It moves while the backend builds, so read it again rather than trusting a copy, including the copies on this site.
#Published response shapes V8 requirement
| Case | Body |
|---|---|
| Success with data | { "data": { … } } — and { "data": …, "message": "…" } after a change, with the message already written and translated |
| Success without data | { "message": "…" } |
| Failed sign-in state | { "message": "…", "code": "unauthenticated" } (401), account_banned / email_not_verified (403) |
| Validation | { "message": "…", "errors": { "field": ["…"] } } (422) — field names match the request |
| Refused delete | { "message": "…", "code": "delete_protection_enabled" } (423) |
| Blocked for the personal API token | { "message": "…", "code": "api_token_not_allowed" } (403) — the token may only view the profile |
| Needs a verified email | { "message": "…", "code": "email_not_verified" } (403) — only on the four guarded actions (invitations, ownership transfer, creating a server, promo credits), not on ordinary calls |
| Too many attempts | { "message": "Too many attempts. Please try again in N seconds." } + Retry-After (429) |
| Lists | { "data": [ … ], "links": {first,last,prev,next}, "meta": {current_page,from,last_page,per_page,to,total} } — Laravel's standard paginator |
Two rules follow from this, and they save work:
- Show the backend's
messageinstead of writing our own. The messages are already final and already translated throughAccept-Language, so a second English string in our dictionary would only drift. Our own copy is for things the API cannot know — labels, empty states, confirmations. - Sensitive actions are rate-limited much harder than the rest — changing a password or deleting the account is 5 a minute, requesting an email change is 3 per 10 minutes, ordinary reads are 60 a minute. The limits themselves are the backend's business, but the 429 message carries the number of seconds, so those forms need a real waiting state (disabled button + the seconds), not a generic "something went wrong".
#Response conventions (proposed for sa-central-api-2)
Follow the OSS conventions so both APIs feel the same Assumption:
- Named top-level keys (
{"servers": [...]}), no genericdatawrapper. - Errors:
{"message": "...", "code": "...", "errors": {...}, "reference": "..."}. - Pagination
meta: {current_page, per_page, total, last_page},?page=&per_page=. - Timestamps: ISO-8601 UTC (frontend formats). OSS uses
DD-MM-YYYY HH:mm:ss, which the proxy passes through or converts Open question. - Auth error codes are published and stable V8 requirement — Phase 2 was built on 2026-10-09 with
email_not_verified,account_banned,ip_not_approved,two_factor_expired,session_expired,unauthenticated. Map these to frontend keys by code, never by message text. (This site had guessedunverifiedandbanned; both were wrong.) - Never retry or parallelise a token refresh V8 requirement — the built refresh token is rotated on every use, and reusing a spent one revokes the whole login chain (2.9). So a retried refresh, or two requests refreshing at once, will log the user out of everything. The BFF must serialise refresh through a single in-flight promise and never auto-retry it; a failed refresh means go to login, not try again.
- Treat every id as a string V8 requirement — Phase 1 step 1.1 (2026-10-08) stores UUIDs as
char(36), so an id can be a 36-character string rather than a number. NeverparseIntan id, never sort by it numerically, and keep route params as strings. This costs nothing where ids are integers and saves a class of bug where they are not. - Text comparison on the backend is case-insensitive — 1.1 sets the collation to
utf8mb4_unicode_ci. So a uniqueness check treats MyApp and myapp as the same name Assumption. Duplicate-name errors should say the name is taken, not hint that different capitalisation would work. And because the charset isutf8mb4, names and labels can legitimately contain emoji and other 4-byte characters — so truncate by character, never by byte.
Proposed error shape (same as OSS)
// 422
{"message": "The given data was invalid.", "code": "validation_failed",
"errors": {"email": ["The email has already been taken."]}}
// 403 (plan)
{"message": "Your plan allows 5 servers.", "code": "plan_limit_reached",
"limit": 5, "used": 5}
// 504 (proxied server)
{"message": "This server is not responding.", "code": "server_unreachable",
"server_id": 42, "reference": "3f9a…"}#API dependency table
Built V8 rows use published contracts under /api/v1; other rows remain V7 references, not V8 contracts. Pair 1's API reference now covers Organization, Members, Roles, permissions/menu and activity (§38). The "V7 reference" column shows the existing V7 equivalent. It's not a V8 contract, and V8 paths are decided per phase. OSS rows exist now.
| Module | Capability | V7 reference (method path) | Auth | Org-scoped | V8 status |
|---|---|---|---|---|---|
| Auth | Register | Under /api/v1/auth/* — the exact path is not published (the API reference covers Pair 2's endpoints only, and auth is Pair 1's). What is published: it returns tokens, the user and the organizations, exactly like login (2026-10-09) | public | no | V8 requirement 2.1 built |
| Auth | Verify email / resend | GET /verify/{token}, POST /resend/verification-link | public | no | Missing |
| Auth | Login | POST /login | public | no | Missing V8 requirement 2.3 |
| Auth | 2FA verify / resend | POST /two-factor-authentication/verify, /resend | public (pending login) | no | Missing |
| Auth | Social login | POST /users/{provider}/url, GET /users/{provider}/callback | public | no | Missing |
| Auth | Forgot / reset | POST /forgot-password, POST /reset-password | public | no | Missing |
| Auth | IP approval | GET /user/whitelist-ip/{key}/authorize | public (signed) | no | Missing |
| Auth | Refresh token | Central's own (Sanctum has none) | refresh token | no | Missing V8 requirement 2.9 |
| Auth | Logout | GET /user/logout | user | no | Missing |
| Auth | Current user | GET /me | user | no | Missing |
| Account | View / update profile | GET /account/profile, PATCH /account/profile | user | no | Confirmed V8 requirement 3.1, 3.2 built |
| Account | Change password | PUT /account/password | user | no | Confirmed V8 requirement 3.4 built |
| Account | Change email | POST /account/email, then POST /account/email/confirm (no token — public) | user / public | no | Confirmed V8 requirement 3.3 built |
| Account | Email preferences | PATCH /account/email-preferences | user | no | Confirmed V8 requirement 3.5 built |
| Account | Delete protection | PUT /account/delete-protection | user | no | Confirmed V8 requirement 3.6 built |
| Account | Delete account | DELETE /account (JSON body: password + optional reason) | user | no | Confirmed V8 requirement 3.7 built |
| Account | Login history | GET /account/login-history?page=&per_page= | user | no | Confirmed V8 requirement 3.11 built |
| Account | Active sessions | GET /account/sessions | user | no | Confirmed V8 requirement 3.16 built |
| Account | Log out one device | DELETE /account/sessions/{id} — allowed for the current device | user | no | Confirmed V8 requirement 3.16 built |
| Account | Log out all other devices | POST /account/sessions/logout-others — returns how many | user | no | Confirmed V8 requirement 3.16 built |
| Account | 2FA status | GET /account/two-factor — enabled, method (null / email / authenticator), backup-code counts | user | no | Confirmed V8 requirement 3.8 built |
| Account | Turn email 2FA on / off | POST /account/two-factor/email (no password) · DELETE /account/two-factor (password) | user | no | Confirmed V8 requirement 3.8 built |
| Account | New backup codes | POST /account/two-factor/backup-codes (password) — six codes, shown once | user | no | Confirmed V8 requirement 3.8 built |
| Account | Authenticator setup / confirm / off | POST /account/two-factor/authenticator → …/authenticator/confirm (6-digit code) · DELETE …/authenticator (password) | user | no | Confirmed V8 requirement 3.9 built |
| Account | IP whitelist list | GET /account/ip-whitelist?page=&per_page= — rows plus enabled and current_ip; each row approved or pending | user | no | Confirmed V8 requirement 3.10 built |
| Account | IP whitelist on / off | PUT /account/ip-whitelist (no password) — off removes every IP | user | no | Confirmed V8 requirement 3.10 built |
| Account | Add / remove an IP | POST /account/ip-whitelist/ips (max 100, only while on) · DELETE /account/ip-whitelist/ips/{id} — tells you if it was the current IP | user | no | Confirmed V8 requirement 3.10 built |
| Account | API access (excluded from the frontend) | GET /account/api-access · POST /account/api-access (on) · POST …/regenerate · DELETE /account/api-access (off) | user | no | Confirmed V8 requirement 3.13 built |
| Account | Activity log | GET /account/activities?page=&per_page=&type=&action= — rows carry a ready translated sentence and the filter options come back with the response; login token only (the API token gets 403) | user | no | Confirmed V8 requirement 3.12 built |
| Notifications | In-app list | **`GET /account/notifications?filter=unread | all&page=&per_page=** — each row brings its own **title, message, icon, link and actor**; unread_count` comes with it | user | no |
| Notifications | Unread count | GET /account/notifications/unread-count — cheap, and the backend says to poll it for the bell | user | no | Confirmed V8 requirement 3.14 built |
| Notifications | Mark one / all read | POST /account/notifications/{id}/read · POST /account/notifications/read-all — both return the new unread count | user | no | Confirmed V8 requirement 3.14 built |
| Organizations | List/create/show/update/delete/default | GET/POST /organizations, GET/PATCH/DELETE /organizations/{organization}, POST …/default | login; manage for update, owner for delete | yes | V8 requirement B4.1/B4.2 built; list search(max100)/type all,mine,invited; membership_status joined/pending, no pagination; pending no access/token; profile/login joined-only; server guard later |
| Organizations | Ownership transfer | GET/POST/DELETE /organizations/{organization}/ownership-transfer, POST …/confirmations, POST …/accept | owner starts/cancels; selected recipient accepts; writes verified | yes | V8 requirement B4.7 built including deletion safety; preview email delivery unverified; unpaid-charge/recipient-plan guards not enforced yet |
| Organizations | Visible menu | GET /organizations/{organization}/menu — sorted visible items; substitute URL placeholders from context | any member | yes | V8 requirement B4.11 built; separate deny-reason map not promised |
| Members | List/update/remove/leave | GET /organizations/{organization}/members?search=&page=&per_page=, PATCH/DELETE …/members/{member}, POST …/leave | member-view/manage; any member may leave, not owner | yes | V8 requirement B4.4 built; 25/page, max 100; owner first then name |
| Members | Accept invitation | POST /user/password-set/{token} or register with invitation_token | public token | no | Missing |
| Roles | CRUD | GET/POST /organizations/{organization}/roles, GET/PATCH/DELETE …/roles/{role} | role-view/manage | yes | V8 requirement B4.5 built; fixed roles immutable; in-use deletion refused |
| Roles | Permission catalog | GET /organizations/{organization}/permissions — parent, requires, owner-only, order | role-view | yes | V8 requirement B4.9–B4.11 built |
| Plans | Catalog | none in V7 (hard-coded) | user | ? | Missing |
| Subscription | Show + usage | GET /organizations/{org}/new-subscription-plan | owner | yes | Missing |
| Subscription | Create / change / renew / cancel / resume / quote | POST /create-subscription, PATCH /change-subscription, /renew-subscription, /cancel-subscription, /resume-subscription, GET /remaining-credit | owner | V7: user | Missing |
| Billing | Enabled gateways | GET /account/payment-gateways, no input; provider/name/mode only | login; unverified allowed; API tokens403 | no | V8 requirement B6.1 Pair2 built/pushed; no keys or checkout; empty valid;60/min |
| Billing | Wallet balance | GET /account/wallet, no input | own active login; unverified allowed | no | V8 requirement B6.3 built/pushed;USD exact decimal strings20 places+as_of;no-store; referenced500≠zero; no public mutations |
| Billing | Add-credit quote/start | POST /account/payment-quotes, POST /account/payments | own active login; unverified allowed | no | V8 requirement B6.4 local/not activated; exact quote/operation key,503 reference |
| Billing | Transactions, receipt | GET /payment, GET /payment-receipt/{key}/receipt | user | V7: user | Missing |
| Billing | Payment status/confirm | GET /account/payments/{UUID}, POST …/confirm empty body+operation key | own login, other owner404 | no | V8 requirement B6.4 local/not activated; no browser-success inference |
| Billing | Cards, auto-recharge | /payment-detail/cards*, /stripe-auto-charge | user | no | Missing |
| Billing | Invoices | GET /organizations/{org}/invoices, POST …/{number}/{action} | owner | yes | Missing |
| Billing | Billing details | GET/PUT /account/billing-details, full form, first save200 | active own-account login; unverified allowed; no org-role gate | no | V8 requirement B6.2 Pair2 built/pushed; null before first save; referenced500 must not become empty/overwrite;60/min |
| Servers | List / connect / show / disconnect | (V7 servers were agent-based, not reusable) | member + perm | yes | Missing |
| Servers | Proxy to OSS | — | member + perm | yes | Missing (D-10) |
| Dashboard | Aggregates | — | member | yes | Missing |
| Providers | List accounts | GET /organizations/{organization}/cloud-provider-accounts?page=&per_page=&search=&provider=&status= | organization.integrations.cloud-providers (was organization.integrations.cloud-providers, renamed 2026-10-17) view | yes | V8 requirement B5.1 built Pair 1, 10/page max 50; credentials/usage counts absent. Connect/Edit built Pair1; Disconnect endpoint partial, usage guard/warning pending |
| Providers | Connect account | POST /organizations/{organization}/cloud-provider-accounts: name/provider/nested credentials | integration-manage | yes | V8 requirement B5.2 built Pair1; 422 rejected vs503 temporary failure; no persistence on failure; real-provider success unverified |
| Providers | Edit account | PATCH /organizations/{organization}/cloud-provider-accounts/{cloudProviderAccount}: name and/or complete credentials | integration-manage; scoped IDs | yes | V8 requirement B5.3 built Pair1; rename no status reset;422/503 no changes;409 reload/review; never auto-retry |
| Providers | Check saved credentials | POST /organizations/{organization}/cloud-provider-accounts/{cloudProviderAccount}/check, no input | integration-manage; scoped IDs | yes | V8 requirement B5.4 built Pair1, on-demand only;200 Connected/Needs new token;503 unchanged/unreadable→Edit;409 reload;shared10/min |
| Providers | Disconnect — partial | DELETE /organizations/{organization}/cloud-provider-accounts/{cloudProviderAccount}, no input | integration-manage; scoped | yes | V8 requirement B5.5 endpoint built Pair1;200 deleted metadata snapshot/repeat404; no upstream revoke/server delete;usage guard/warning NOT built |
| Providers | Regions / sizes | …/{id}/regions, …/{id}/sizes | member + perm | yes | Missing |
| Providers | Create VPS | POST /organizations/{org}/servers (V7 store) | member + perm | yes | Missing |
| Blueprints | CRUD, upload, WP.org search | /wordpress-blueprints* | user | V7: user | Missing |
| Audit | Organization list | GET /organizations/{organization}/activity?page=&per_page=&type=&action= — translated descriptions, actor snapshots, filters, paginator | organization.activity view | yes | V8 requirement B4.8 built, Pair 1; 10/page, max 50 |
| Search | Global | (V7 route disabled) | member | yes | Missing |
| Notifications | Channels + send test | GET/POST /account/notification-channels, GET/PATCH/DELETE …/{id}, POST …/{id}/test | login only; unverified allowed | no | V8 requirement B3.15 built; test202 queued, poll detail; recorded≠delivered; no saved-count cap |
| Notifications | Future routing/attachments | No published B3.15 attachment/event-group API | user/server permission later | server later | Missing Phase11/Servers, not channel CRUD |
#OSS endpoints Central calls (exist today) OSS API
| Purpose | Method + path | Auth |
|---|---|---|
| Health / version | GET /api/health | none |
| Validate key / identity | GET /api/auth/me | Bearer central key |
| Facts / metrics / processes | GET /api/server/facts, /server/metrics/live, /server/metrics/history, /server/processes | Bearer |
| Capabilities | GET /api/server/capabilities | Bearer |
| Applications, databases, system users, services, firewall, cron, backups, PHP, Node, Fail2ban, settings, logs, disk cleaner, sync | see Server details | Bearer |
| Paid add-ons | GET /api/central/addons, GET /api/central/addons/runs/{run}, /api/central/addons/applications/{id}/wordpress/*, /log-monitoring/* | Bearer (Central only) |
| Install with key | CENTRAL_TOKEN=… bash install.sh --stack=… | installer env |
| Not available to Central | POST/DELETE /central (enable/revoke) needs an OSS admin session; user/role mutations → 403 | — |
Provider code availability (2026-10-10): Pair2 reports B5.1–B5.5 pulled through cf36220;1211 tests/7473 assertions, Pint/PHPStan and optimization passed after pull (not rerun by this sync). List/Connect/Edit/Check and partial Disconnect are present in its copy. No genuine provider success is established. Disconnect server-usage blocker/server-name warning remain unbuilt until Phase8; Git/storage still wait Servers.
| Billing | Free-credit redemption | POST /account/promo-redemptions code+operation key | own login, verified required | no | V8 requirement B6.4 local; no tax/gateway/+3 |
Provider webhooks API48 are setup-only, not frontend calls; acknowledgement is intake, not paid credit. Return route /verify-transaction/{payment UUID} and Billing destination must be coordinated before activation.
#Built administrator entry (Pair1 B17.1, API49–50)
| Capability | Endpoint | Access | Readiness |
|---|---|---|---|
GET /api/v1/account/admin-access | active login, unverified allowed; no personal API token; own role/flag only | V8 requirement Pair1 built, flag for navigation not authorization | |
GET /api/v1/admin/me | active verified exact administrator login; current state checked | V8 requirement own identity only; permissions/menu/business APIs still missing |
No-store responses.401 unauthenticated;403 admin_access_denied/email_not_verified/api_token_not_allowed/account_banned as appropriate;429 shared60/min/account. Normal login/profile format unchanged. No HTTP promotion endpoint or global organization bypass. Pair2 code availability unconfirmed. Entry screen.
2026-10-12 latest readiness: Pair1 B17.2/17.2a adminroles/catalog/assignment APIs51–58 BUILT, menu/sidebar17.3 not built. Pair2 B6.5 savedcards/default/remove,autorecharge/consent/same-payment recovery APIs49–62 IN PROGRESS—notcomplete/committed/pushed/activated; safety fixes and final verification ongoing. Admin contracts/screens; Billing drafts/states. Do not confuse Pair1API49–58(admin) with Pair2API49–62(cards).
Latest B6.5 status (2026-10-12 10:35): 6.4 + 6.5 pushed to GitHub; later local follow-ups: card APIs answer directly (0ff3da9) and auto-recharge is not available for India (billing country or India-issued card, as V7) — the India mandate API (old 62) was removed (e65e8c3). Real payments still not activated; no real keys/charges or host scheduler change. Frontend/consent/provider proof still required.
Removed 2026-10-16 (Pair 1): GET /account/admin-access and GET /admin/me no longer exist. Admin entry = role / is_administrator from the login/profile response + the filtered sidebar GET /admin/menu (API59). The admin's role and permissions now come with /admin/menu (admin_role, permissions — added 2026-10-16 05:22).
Latest B17.3 (Pair1): GET /api/v1/admin/menu built, active verified administrator; role-filtered translated group/title/Lucide/localURL/order/parent, no role→empty. 59 permissions, 34 sidebar screens at that time — now 53 permissions / 33 sidebar screens after B17.11 (2026-10-16); tabs/actions no URLs. Queue-size page/permission removed, Horizon instead. B17.4+ business/viewer unbuilt; menu≠working destination, Pair2 availability unconfirmed. Menu states.
B17.4 latest: Pair1 admin Activity log API60 GET /api/v1/admin/activity BUILT; active verified administrator+admin-activity view, private/no-store. Table/filters/pagination/snapshot targets read-only, separate admin feed; account/org feed unaffected. B17.5+ still unbuilt,Pair2unconfirmed. Activity screen.
B17.6 latest (2026-10-12): Pair1 admin Users API61–68 BUILT — list/counts/view/create/ban/unban/verify/delete, deleted users, referred users; statuses active/pending/banned (no locked); admins can't ban/delete themselves or other administrators. Users are now soft deleted (also self-delete B3.7) — the email can still sign up again, nothing changes on screen. B17.5 and B17.7+ still unbuilt, Pair2 unconfirmed. Users screens.
B17.7 latest (2026-10-12): Pair1 admin User details API69–76 BUILT — organizations, login history, account activity, 2FA status/turn off (backup codes counted only), IP whitelist view/turn off/remove IP; new users.activity view permission (60 admin permissions); servers tab waits for Phase 8. Admin changes show in the user's activity as by a ServerAvatar admin. B17.5 and B17.8+ still unbuilt. User details.
B17.10 latest (2026-10-14): Pair1 Login as user API77 BUILT — users.login-as manage only; normal user login without password/2FA/IP whitelist; refused for own account/administrators/banned users; recorded only in admin activity. Login as user.
B17.15 latest (2026-10-14): Pair1 admin Promo codes API78–81 BUILT — list/create/on-off/delete (soft, name stays taken, history kept); no edit after creation; cohort renamed customer_type (all/new/existing). Promo codes.
B17.17 latest (2026-10-16): Pair1 admin Site settings API93–97 BUILT — basic details (branding, uploads, custom header/footer, links) + company billing details (tax % = checkout tax); public GET /site-settings before login. D-64 open (custom code). Site settings.
B17.12 Part 1 (2026-10-17): Pair1 admin Servers API114–119 BUILT — list/view/edit (name, hostname, IP) + user/organization Servers and Deleted servers tabs; live parts later. Servers are now soft deleted (hidden from users). Deleted list = D-63 conflict. Admin servers.
B17.19 (2026-10-17): Pair1 admin General settings API103–113 BUILT — display message (banner for every user via API97 display_message), trial mode on/off (not used — V8 has no trial, D-56), referral program settings, Slack webhook for admin alerts. User settings / temporary domain / support email removed (57 permissions, 31 screens). General settings.
B17.23 (2026-10-16): Pair1 admin Error monitor API99–101 BUILT — list/view/resolve unhandled request/job/command errors (secrets removed, kept 2 days, reopen on repeat). Error monitor.
B17.6 edit (2026-10-16): Pair1 admin Edit a user API98 BUILT — profile, email (no confirmation), password, role, admin role, status for any account incl. administrators; lockout guard. Edit a user.
B17.20 Part 1 latest (2026-10-14): Pair1 admin Integration keys API82–88 BUILT — social login (GitHub/Google), Turnstile, payment gateways (Stripe/PayPal/Instamojo, test/live keys, webhook URLs); secrets write-only. SMTP + test mail (Part 2) next. Integration keys.
B17.20 Part 2 (2026-10-15): Pair1 admin SMTP + test email API89–92 BUILT (saved vs server-default settings, write-only password, test email with short failure reason, reset to defaults). Integration keys complete except cloud platforms (to confirm). SMTP.