Skip to content

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:

  1. Day 0quotaExceededAt is stamped on the runtime state doc.
  2. 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).
  3. Day 7+ — grace expired. New seat claims (agents not already in the hour’s counted set) get HTTP 403 + upgradeUrl when 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:

  1. Increases seatQuota immediately (via ARM PATCH on the customer Function App’s SEAT_QUANTITY setting).
  2. Stamps quotaGraceResetAt on the deployment doc. The next seat-guard evaluation clears quotaExceededAt.

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 / seatQuota and a banner during grace.
  • Via the API: GET /api/plan returns 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.