Seat management + grace period
import { Aside } from “@astrojs/starlight/components”;
A seat is a distinct human agent (identified by their AAD object id) who has claimed at least one session within the last hour. The check window is rolling — it does NOT reset at midnight.
Counting rules
- Claim a session → if you’re not already counted in the current hour, you consume a seat.
- Already counted this hour → claiming additional sessions does NOT consume new seats. One agent handling 5 chats is still 1 seat.
- Close a session → no immediate seat release. Your hour-window claim still counts you for the rest of the hour.
- Idle for 60 minutes (no new claims) → your seat is freed.
This matches typical contact-center “concurrent agents” semantics.
Quota and grace
When seatsUsed > seatQuota:
- Day 0 —
quotaExceededAtis stamped on the runtime state doc. - Days 1-6 — grace period. All agents continue to claim. Dashboard shows a yellow banner with days remaining. You and your billing contact receive a “Grace period started” email (one per cycle).
- Day 7+ — grace expired. New seat claims (agents not already in the hour’s counted set) get HTTP 403 +
upgradeUrlwhen they try to claim. Existing-claim agents continue to work.
When seatsUsed <= seatQuota returns, quotaExceededAt is cleared and the grace counter resets.
Plan upgrade resets the grace counter
Upgrading from Start → Growth (or Growth → Professional) does two things:
- Increases
seatQuotaimmediately (via ARM PATCH on the customer Function App’sSEAT_QUANTITYsetting). - Stamps
quotaGraceResetAton the deployment doc. The next seat-guard evaluation clearsquotaExceededAt.
So upgrading rescues you from a grace period: no new emails fire, no agents are blocked.
What about downgrades?
Downgrading (Professional → Growth) doesn’t immediately block any of your over-quota agents — the grace period starts fresh. You have 7 days to either drop back below quota OR re-upgrade.
Seeing your current status
- In the dashboard: the Plan & Seats tile shows
seatsUsed / seatQuotaand a banner during grace. - Via the API:
GET /api/planreturns the full state:
{ "planId": "growth", "planDisplayName": "Growth", "seatQuota": 7, "seatsUsed": 6, "exceeded": false, "daysOverQuota": 0, "gracePeriodDays": 7, "blocked": false, "upgradeUrl": "https://portal.azure.com/..."}This endpoint is anonymous (same-origin only — used by the dashboard JS).
How seats interact with billing
Microsoft Marketplace bills you the flat plan fee monthly regardless of seats used. The seat count is not a per-seat usage charge — it’s a quota enforced by us.
Optional metering dimensions (sent from the customer-stack to the ISV control plane via seat-report):
- seats — current concurrent agent count, hourly snapshot. Microsoft sees this for usage-based reporting but doesn’t bill against it under any of our three plans.
- tokens-output — total output tokens to Azure OpenAI in the last hour. Similar — reported for visibility, not billed under our plans.