What we shipped, in date order.
A running record of every feature that made it onto STRALO: schema, endpoints, workers, the dashboard, and the surfaces that wrap them. Newest first. Each title has a stable anchor so /changelog#… links survive copy edits.
#Recoverable claimed bootstrap guidance
The dashboard now explains that first-agent bootstrap is one-time when the slot is already claimed, directs retained-key users to existing-key login, and points operators to stralo@polsia.app for credential rotation when the original plaintext key was not saved; one-time credential issuance remains unchanged.
Read more →#Dashboard first-agent bootstrap fix
Fixed the first-agent bootstrap form runtime error and clarified the dashboard’s initial authentication response so a newly created agent can save its one-time key and unlock the scoped dashboard.
Read more →#First-agent MCP bootstrap
Brand-new MCP connections can now claim the first agent with explicit bootstrap:true, receive its public_token once with save-and-forward guidance, and use that key for authenticated REST and MCP calls; repeated claims remain a non-secret 409 conflict.
Read more →#MCP transport documentation
Added a complete Streamable-HTTP guide for /api/mcp, including discovery, JSON-RPC initialization, session acknowledgement, tools/list requests, Node fetch examples, authentication, and the eight shipped tool names.
Read more →#stralo-js SDK v0.2 — webhook verification helper
Added `verifyWebhookSignature(rawBody, signatureHeader, secret)` to `stralo-js@0.2.0`, using the documented five-minute HMAC verification scheme so subscribers can authenticate `booking.reminder` and other outbound events before parsing them; see /docs#webhook-events.
Read more →#stralo-js SDK v0.2 — agents and proposal lifecycle wrappers
Shipped `stralo-js@0.2.0` with typed `agents.create(input)`, `proposals.accept(id)`, and `proposals.reject(id)` wrappers. Agent creation mirrors the OpenAPI `AgentCreate` → `Agent` contract, sends the documented idempotency key, captures the one-time `public_token`, and maps structured 401 unauthorized and 409 conflict responses through `StraloError`.
Read more →#STRALO as an MCP tool
Published a practical guide for agent developers covering MCP discovery, the eight scheduling tools, X-API-Key authentication, REST bookings and proposals, and a JSON-RPC tools/call against /api/mcp.
Read more →#stralo-js SDK v0 — typed wrapper for POST /bookings + POST /proposals
Shipped `stralo-js@0.1.0`, a public npm package that hands callers a typed `createClient({ apiKey })` factory plus `bookings.create` and `proposals.create` wrappers. Every method shapes the request body from the openapi `BookingCreate` / `ProposalCreate` contract and parses the response from `BookingItem` / `ProposalItem`, so the SDK stays in lock-step with the live /openapi.json schema; 4xx responses surface as a typed `StraloError` (codename `'unauthorized' | 'slot_taken' | 'proposal_not_pending' | 'bad_request' | 'not_found' | 'forbidden' | 'booking_cap_reached' | 'internal'`), pulling `baseUrl` from `STRALO_BASE_URL` when the caller does not pass one explicitly. The package ships at zero runtime cost (no `zod`, no transport library), tests cleanly on Node ≥20, and the install + quickstart example on the package README walks a consumer from `npm install` to the first booked slot in eight lines — see /docs#quickstart for the in-route API reference or grab the SDK from the public npm registry.
Read more →#MCP public URL: connection endpoint clarified at /api/mcp
Verified end-to-end that a generic MCP-capable client handed https://stralo.dev/mcp.json now opens a usable connection: the discovery manifest advertises https://stralo.dev/api/mcp as the Streamable-HTTP transport (not the previous relative `/mcp`), so client + server agree on a single absolute URL — no second round-trip to derive the base path. On /api/mcp the `initialize` JSON-RPC frame returns protocolVersion '2025-03-26', serverInfo.name = 'STRALO', capabilities.tools = {}, and a Mcp-Session-Id header; the follow-up `notifications/initialized` acks with 202; `tools/list` returns 8 tools matching the inner REST surface; `tools/call` then issues POST /api/agents etc. inside the same Bearer channel the REST surface uses. The eight REST routes are unchanged; only the manifest's transport.url changed shape.
Read more →#Proposal accept/reject webhook events
PATCH /api/proposals/{id}/accept and /reject now fan out proposal.accepted and proposal.rejected webhooks — one per terminal PATCH, signed with the same X-Polsa-Signature (Stripe-style, t=…,v1=…, HMAC of TIMESTAMP_DOT_RAWBODY) + X-Polsa-Delivery-Id + X-Polsa-Event-Type headers the booking.* webhook already uses. Each event payload carries proposal id, recipient agentId, counterpartyId, decision ('accepted'|'rejected'), and a UTC decidedAt timestamp — see /docs#webhook-events for the per-event shape and the verification recipe. Status-pull, repeated PATCHes, and helper failures are unchanged.
Read more →#MCP server live at /mcp and /api/mcp
STRALO is now reachable as a standards-compliant MCP server, not only a static JSON manifest. /mcp.json advertises per-tool endpoints + a Streamable-HTTP transport hint, and the new /api/mcp endpoint speaks the JSON-RPC 2.0 protocol (initialize + notifications/initialized + tools/list + tools/call + ping) so an MCP-capable agent that fetched /mcp.json can establish a session, enumerate the eight live tools, and invoke them with the same Authorization: Bearer <sk_…> header that the REST surface already honors (no parallel credential channel). All eight REST routes — POST /agents, POST + GET /bookings, DELETE /bookings/{id}, GET /bookings/{id}/occurrences, POST /proposals, PATCH /proposals/{id}/accept and /reject — are preserved and unchanged; the MCP transport is a thin wrapper over the same handlers.
Read more →#Status page — live API + webhook health
Shipped /status: green "operational" badge driven by the most-recent successful-processing timestamp (WebhookDelivery / Booking / BookingReminder), 90-day uptime ratio pulled from WebhookDelivery, and a 24-hour success-rate readout for booking.reminder fanout — all fetched live from the same DB the webhook-delivery cron writes. Refreshed on every visit, not a static uptime widget.
Read more →#MCP server manifest at /mcp.json
/mcp.json is now reachable from a logged-out browser, returns application/json, conforms to the current MCP `server` + `tools` shape, and exposes the eight live STRALO endpoints (POST /agents, POST + GET /bookings, DELETE /bookings/{id}, GET /bookings/{id}/occurrences, POST /proposals, PATCH /proposals/{id}/accept, PATCH /proposals/{id}/reject) with method, path, auth, and input schema per tool — so any MCP-capable agent can enumerate and call the surface programmatically. POST /api/stripe/webhook is intentionally absent: STRALO does not run an inbound Stripe callback endpoint (Stripe events reach the app via the Polsia payment proxy's payment-events feed, the same posture as the /docs#payments-model section and the stripe-billing module).
Read more →#tstzrange EXCLUDE vs. application-level locks
Shipped the second long-form post: a side-by-side of Postgres tstzrange EXCLUDE (the schema-level gate STRALO ships) against the SELECT … FOR UPDATE / advisory-lock / SERIALIZABLE patterns most code reaches for first — TOCTOU windows, lock-table explosion under contention, deadlock retry storms, and the durability story a schema-level constraint buys — with a worked two-writer POST /bookings race showing only one writer can commit.
Read more →#FAQ page — topic-anchored accordion
Refreshed /faq into a category-grouped TOC plus anchor-linked accordion: ten code-grounded Q&As across Integration, Bookings & proposals, Webhooks, Billing, and Lifecycle, with stable #<slug> deep links per question and per category — including the EXCLUDE constraint, API-key lifecycle, booking.reminder failure-alert cron, tier-cap coupling, and the proposal accept/reject flow.
Read more →#Booking-reminder webhook failure alerts
When a `booking.reminder` delivery exhausts all six attempts, the reminder tick reconciles recent failed delivery rows and sends one deduplicated owner alert through the Polsia email path — with booking id, target URL, last HTTP status, and last-attempt timestamp included. The `WebhookDelivery.failureAlertSentAt` sentinel is shared by the inline and retry-cron paths, so a worker overlap or later reminder tick never sends a duplicate for the same failed episode.
#Dashboard plan & usage card
The /dashboard surface now carries a "Plan & usage" card that surfaces the agent’s current subscription tier (Free / Pro / Swarm) and booking cap, fed by `Agent.tier` + `Agent.bookingCap` that the `subscription-sync` cron already flips on every Stripe webhook event. The tier shaping reads the server-only pricing catalog server-side so no client import of `server-only` is needed.
Read more →#Dashboard 30-day analytics card
The /dashboard surface now carries a per-agent "Analytics — last 30 days" card with two counters: confirmed bookings created and 409 slot_taken rejections (audited via a new BookingRejection table seeded from insert-booking). Both are scoped to the cookie-authenticated agent only — no cross-agent roll-up.
#Booking reminder webhooks
Confirmed bookings now fan out a `booking.reminder` webhook one hour before start time; the reminder-cron claims each booking once via a unique BookingReminder row and the existing webhook-delivery worker signs + retries the rest.
#Pricing page
Shipped /pricing — three tiers (Free / Pro / Swarm) with a hosted Stripe checkout for the paid tiers; the success banner verifies the session id and the downgrade is reversible from the Stripe customer portal.
Read more →#Stripe webhook tier-flip
Verified end-to-end: a successful checkout flips the matching Agent's tier (free → pro → swarm) and bookingCap, and the subscription-sync cron re-checks every paid agent on a 5-min tick so cancellations downgrade automatically.
#DELETE /bookings
DELETE /api/bookings/[id] soft-cancels the bearer's own booking, fires a `booking.cancelled` webhook, and frees the slot under the existing exclusion without deleting the audit row.
#Proposal accept/reject PATCH verbs
Added PATCH /api/proposals/[id]/accept (transfer slot ownership to the proposer) and PATCH /api/proposals/[id]/reject (the original owner resets the proposal to `pending`); both verbs share the bearer-must-be-target check and 409 on already-decided rows.
#Agent dashboard experience
The dashboard surface now pairs the bearer-key-as-cookie login with rotate, summary, and webhook secret endpoints posted under /api/dashboard/* so the same agent can self-serve from the front-end.
Read more →#First blog post
Published the first long-form post explaining how STRALO enforces non-overlap at the schema, link available on request.
#FAQ page
Shipped /faq — a code-grounded answer sheet covering machine credentials, overlap rejections, webhook fanout, rotation, and pricing posture.
Read more →#RRULE read support
GET /api/bookings now accepts an RFC 5545 RRULE so callers can fetch the recurring window of a single agent in one request.
#One-hour cron tick
Bumped the housekeeping cron to a one-hour cadence so failed webhook deliveries and expiry sweeps drain within the hour instead of overnight.
#Agent dashboard
Shipped /dashboard — the same agent key, now usable as an httpOnly cookie, reveals the bookings and webhook deliveries that key already owns.
Read more →#Outbound webhook worker
Confirmed bookings now fan out to per-agent webhook URLs via an async worker that retries with exponential backoff up to six attempts.
#Proposal endpoints
Added POST /api/proposals and POST /api/proposals/[id]/accept so an agent can offer a slot for human sign-off before the booking is committed.
#DELETE /bookings
Cancelled bookings transition to status="cancelled" through DELETE /api/bookings/[id]; the audit row is preserved and re-booking the same window becomes possible.
#GET /bookings
Read-side landed: GET /api/bookings returns an agent-scoped list with status, RRULE, and overlap-safe time windows.
#POST /bookings
Write-side landed: POST /api/bookings inserts against the bookings_no_overlap exclusion so two agents cannot collide on the same window.
#POST /agents
Agents are now first-class rows: POST /api/agents issues one opaque sk_<uuid> per agent and stores only its SHA-256 in the database.
#Postgres schema
Initial Postgres schema with the bookings_no_overlap exclusion constraint, on top of which every later endpoint enforces the no-collision guarantee.
Want a heads-up before something ships?
New entries land on top of this page the day each feature goes live. If you want to be told there’s a slot first, email stralo@polsia.app — humans monitor it — or jump to /quickstart to drive the REST surface yourself.