Seats
Endpoints for managing AI seat assignments, transfers, and bulk operations.
GET /api/ai-seats
List AI seats with flexible filtering options.
Authentication: permission.tenant.superuser
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
planId | string | Filter by plan ID |
assigned | boolean | Filter by assignment status |
unassigned | boolean | Get users without any seat assignment |
transferEligible | boolean | Get users eligible for seat transfer (no seat or free tier) |
includeAllUsers | boolean | Include all users (with and without seats) for the All Users view |
Response (Standard):
{
"seats": [
{
"id": "seat-123",
"plan": {
"id": "advanced-456",
"name": "Advanced AI",
"slug": "advanced",
"icon": "sparkles",
"weeklyBudget": 2.00,
"sessionBudget": 0.40,
"purchasable": true
},
"pendingPlan": null,
"username": "alice",
"displayName": "Alice Smith",
"assignedAt": "2024-01-10T09:00:00Z",
"assignedBy": "admin",
"weeklyCostUsed": 1.23,
"sessionCostUsed": 0.15,
"weeklyPeriodStart": "2024-01-10T09:00:00Z",
"sessionPeriodStart": "2024-01-15T12:00:00Z"
}
]
}
Response (Unassigned Users):
{
"users": [
{
"username": "bob",
"displayName": "Bob Jones"
}
]
}
Response (Transfer Eligible):
{
"users": [
{
"username": "charlie",
"displayName": "Charlie Brown",
"plan": {
"id": "free-789",
"name": "Free AI",
"slug": "free"
}
},
{
"username": "diana",
"displayName": "Diana Prince",
"plan": null
}
]
}
Response (All Users View):
Returns both seated and unseated users with hasSeat flag.
POST /api/ai-seats
Create unassigned seats for a plan.
Authentication: permission.tenant.superuser
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
planId | string | Yes | Plan ID |
quantity | integer | Yes | Number of seats to create (1-100) |
Example Request:
{
"planId": "advanced-456",
"quantity": 5
}
Response:
{
"created": 5,
"planId": "advanced-456",
"planName": "Advanced AI",
"seats": {
"total": 15,
"assigned": 10,
"unassigned": 5
}
}
GET /api/ai-seats/{id}
Get detailed information about a specific seat.
Authentication: permission.tenant.superuser
Response:
{
"id": "seat-123",
"plan": {
"id": "advanced-456",
"name": "Advanced AI",
"slug": "advanced",
"weeklyBudget": 2.00,
"sessionBudget": 0.40,
"tiers": ["everyday", "advanced"]
},
"pendingPlan": null,
"username": "alice",
"user": {
"username": "alice",
"displayName": "Alice Smith",
"email": "alice@example.com"
},
"assignedAt": "2024-01-10T09:00:00Z",
"assignedBy": "admin",
"weeklyCostUsed": 1.23,
"sessionCostUsed": 0.15,
"weeklyPeriodStart": "2024-01-10T09:00:00Z",
"sessionPeriodStart": "2024-01-15T12:00:00Z",
"boost": {
"budget": 5.00,
"used": 0.75,
"remaining": 4.25,
"expiresAt": "2024-02-10T09:00:00Z"
},
"weeklyRemaining": 0.77,
"sessionRemaining": 0.25
}
PUT /api/ai-seats/{id}
Update a seat (assign/unassign user, change plan).
Authentication: permission.tenant.superuser
Request Body:
| Field | Type | Description |
|---|---|---|
username | string | Username to assign (null to unassign) |
planId | string | Move seat to different plan |
Example Request (Assign):
{
"username": "alice"
}
Example Request (Unassign):
{
"username": null
}
Example Request (Change Plan):
{
"planId": "strategic-789"
}
Response:
{
"id": "seat-123",
"plan": {
"id": "strategic-789",
"name": "Strategic AI",
"slug": "strategic",
"weeklyBudget": 5.00,
"sessionBudget": 1.00
},
"pendingPlan": null,
"username": "alice",
"displayName": "Alice Smith",
"assignedAt": "2024-01-10T09:00:00Z",
"assignedBy": "admin",
"weeklyCostUsed": 0.00,
"sessionCostUsed": 0.00,
"changeType": "upgrade",
"changeMessage": "Plan upgraded from Advanced AI to Strategic AI"
}
Side Effects:
- Assigning a user resets usage counters
- Changing plans may create pending plan change (prorated)
- Syncs changes to License Manager
DELETE /api/ai-seats/{id}
Delete an unassigned seat.
Authentication: permission.tenant.superuser
Response:
{
"success": true
}
Restrictions:
- Cannot delete assigned seats (unassign first)
- Syncs deletion to License Manager
POST /api/ai-seats/_assign
Assign a user to a plan (claims existing seat or creates new one).
Authentication: permission.tenant.superuser
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Username to assign |
planId | string | Conditional | Plan ID or slug (required for non-enterprise, optional for enterprise) |
Example Request:
{
"username": "alice",
"planId": "advanced-456"
}
Response:
{
"id": "seat-123",
"plan": {
"id": "advanced-456",
"name": "Advanced AI",
"slug": "advanced",
"weeklyBudget": 2.00,
"sessionBudget": 0.40
},
"username": "alice",
"assignedAt": "2024-01-15T14:00:00Z",
"assignedBy": "admin"
}
Enterprise Response:
{
"id": "seat-456",
"plan": {
"id": "unlimited-789",
"name": "Unlimited AI",
"slug": "unlimited",
"weeklyBudget": 999.99,
"sessionBudget": 999.99
},
"username": "alice",
"assignedAt": "2024-01-15T14:00:00Z",
"assignedBy": "admin",
"enterprise": true
}
Behavior:
- Enterprise tenants: Creates enterprise seat, defaults to unlimited plan if no planId provided
- Non-enterprise: Claims existing unassigned seat or creates new seat
- Free to paid upgrade: Automatically upgrades free seats to paid plans
- Reassignment validation: Enforces billing period restrictions for purchasable seats
Errors:
409 Conflict- User already has a seat409 Conflict- Seat reassignment not allowed yet (billing period restriction)400 Bad Request- User not found or plan not found
For purchasable seats, this endpoint requests reassignment permission from License Manager to enforce billing period rules.
POST /api/ai-seats/_unassign
Unassign a user from their seat.
Authentication: permission.tenant.superuser
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Username to unassign |
Example Request:
{
"username": "alice"
}
Response:
{
"success": true,
"username": "alice",
"seatId": "seat-123",
"previousPlan": {
"id": "advanced-456",
"name": "Advanced AI",
"slug": "advanced"
},
"unassignedAt": "2024-01-15T14:30:00Z"
}
Side Effects:
- Marks seat as unassigned
- Preserves seat for potential reassignment
- Syncs to License Manager
GET /api/ai-seats/user/{username}
Get the current user's seat and budget status.
Authentication: Required (session)
Pre-blocks: ai.verify
Response:
{
"username": "alice",
"seat": {
"id": "seat-123",
"plan": {
"id": "advanced-456",
"name": "Advanced AI",
"slug": "advanced",
"tier": "advanced",
"weeklyBudget": 2.00,
"sessionBudget": 0.40
},
"assignedAt": "2024-01-10T09:00:00Z"
},
"budget": {
"weeklyUsed": 1.23,
"weeklyRemaining": 0.77,
"sessionUsed": 0.15,
"sessionRemaining": 0.25,
"boostRemaining": 4.25,
"totalRemaining": 5.02
},
"periods": {
"weeklyStart": "2024-01-10T09:00:00Z",
"sessionStart": "2024-01-15T12:00:00Z"
}
}
POST /api/ai-seats/_transfer
Transfer a seat from one user to another.
Authentication: permission.tenant.superuser
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
fromUsername | string | Yes | Current seat holder |
toUsername | string | Yes | New seat recipient |
Example Request:
{
"fromUsername": "alice",
"toUsername": "bob"
}
Response:
{
"success": true,
"fromUsername": "alice",
"toUsername": "bob",
"seatId": "seat-123",
"plan": {
"id": "advanced-456",
"name": "Advanced AI",
"slug": "advanced"
},
"transferredAt": "2024-01-15T15:00:00Z"
}
Validation:
- Source user must have a seat
- Source seat must not be a free plan seat (free seats are not transferable)
- Target user must not have a paid seat (free seats are silently replaced)
- Enforces reassignment rules for purchasable seats
- Requests License Manager permission if needed
POST /api/ai-seats/_bulk-assign
Assign multiple users to a plan in a single operation.
Authentication: permission.tenant.superuser
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
usernames | array | Yes | Array of usernames to assign |
planId | string | Yes | Plan ID for all assignments |
Example Request:
{
"usernames": ["alice", "bob", "charlie"],
"planId": "advanced-456"
}
Response:
{
"success": true,
"assigned": 3,
"failed": 0,
"results": [
{
"username": "alice",
"success": true,
"seatId": "seat-123"
},
{
"username": "bob",
"success": true,
"seatId": "seat-124"
},
{
"username": "charlie",
"success": true,
"seatId": "seat-125"
}
]
}
Partial Success:
If some assignments fail, the response includes error details:
{
"success": false,
"assigned": 2,
"failed": 1,
"results": [
{
"username": "alice",
"success": true,
"seatId": "seat-123"
},
{
"username": "bob",
"success": false,
"error": "User not found"
},
{
"username": "charlie",
"success": true,
"seatId": "seat-125"
}
]
}
POST /api/ai-seats/_bulk-unassign
Unassign multiple users in a single operation.
Authentication: permission.tenant.superuser
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
usernames | array | Yes | Array of usernames to unassign |
Example Request:
{
"usernames": ["alice", "bob", "charlie"]
}
Response:
{
"success": true,
"unassigned": 3,
"failed": 0,
"results": [
{
"username": "alice",
"success": true,
"seatId": "seat-123"
},
{
"username": "bob",
"success": true,
"seatId": "seat-124"
},
{
"username": "charlie",
"success": true,
"seatId": "seat-125"
}
]
}
POST /api/ai-seats/{id}/_boost
Grant boost budget to a seat.
Authentication: permission.tenant.superuser
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Boost amount in USD (minimum $0.01) |
Example Request:
{
"amount": 5.00
}
Response:
{
"id": "seat-123",
"username": "alice",
"boost": {
"budget": 5.00,
"used": 0.00,
"remaining": 5.00,
"expiresAt": "2024-02-15T14:00:00Z"
}
}
Boost Behavior:
- One-time budget added to seat
- Used before weekly/session budgets
- Expires after 30 days
- Cannot be transferred between users
- Admins only
POST /api/ai-seats/{id}/_change-plan
Change an assigned seat's plan with proration.
Authentication: permission.tenant.superuser
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
planId | string | Yes | New plan ID |
strategy | string | No | swap (default) or direct. swap moves user to a seat from the target plan pool. direct changes the plan on the seat itself via License Manager (handles proration, deferred downgrades). |
Example Request:
{
"planId": "strategic-789",
"strategy": "swap"
}
Response:
{
"success": true,
"changeType": "upgrade",
"message": "Plan upgraded from Advanced AI to Strategic AI",
"seat": {
"id": "seat-123",
"plan": {
"id": "strategic-789",
"name": "Strategic AI",
"slug": "strategic",
"weeklyBudget": 5.00,
"sessionBudget": 1.00
},
"pendingPlan": null,
"username": "alice"
},
"proration": {
"creditApplied": 1.50,
"chargeAmount": 3.00,
"netCharge": 1.50,
"effectiveAt": "2024-01-15T14:00:00Z"
}
}
Change Types:
- Immediate upgrade: Applied immediately, proration calculated
- Scheduled downgrade: Takes effect at next billing cycle
- Lateral move: Different plan, same tier
Pending Plan:
Downgrades create a pending plan change:
{
"pendingPlan": {
"id": "everyday-123",
"name": "Everyday AI",
"slug": "everyday",
"effectiveAt": "2024-02-01T00:00:00Z"
}
}
DELETE /api/ai-seats/{id}/_cancel-pending
Cancel a pending plan change.
Authentication: permission.tenant.superuser
Response:
{
"success": true,
"message": "Pending plan change canceled",
"seat": {
"id": "seat-123",
"plan": {
"id": "advanced-456",
"name": "Advanced AI"
},
"pendingPlan": null
}
}
POST /api/ai-seat-purchase
Purchase additional seats with proration and tax estimates.
Authentication: permission.tenant.superuser
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
planId | string | Yes | Plan ID to purchase seats for |
quantity | integer | Yes | Number of seats to purchase |
Example Request:
{
"planId": "advanced-456",
"quantity": 5
}
Response:
{
"success": true,
"planId": "advanced-456",
"planName": "Advanced AI",
"quantity": 5,
"pricePerSeat": 25.00,
"subtotal": 125.00,
"prorationCredit": 12.50,
"taxEstimate": 10.00,
"total": 122.50,
"billingDate": "2024-02-01T00:00:00Z"
}
Purchase Flow:
- Calculate proration based on current billing cycle
- Estimate taxes based on tenant location
- Create seats in Informer
- Sync purchase to License Manager
- Charge via configured payment method
GET /api/ai-seats/consumption
Get seat consumption summary by plan.
Authentication: permission.tenant.superuser
Response:
{
"plans": [
{
"planId": "advanced-456",
"planName": "Advanced AI",
"planSlug": "advanced",
"totalSeats": 10,
"assignedSeats": 8,
"activeUsers": 7,
"weeklyUsage": 8.45,
"monthlyUsage": 32.18,
"averagePerSeat": 4.03
}
],
"totals": {
"totalSeats": 35,
"assignedSeats": 28,
"activeUsers": 24,
"weeklyUsage": 24.67,
"monthlyUsage": 98.54
}
}
Metrics:
- totalSeats: All seats for the plan
- assignedSeats: Seats with users
- activeUsers: Users who made requests this week
- weeklyUsage: Cost this week (USD)
- monthlyUsage: Cost this month (USD)
- averagePerSeat: Monthly cost / assigned seats
Use bulk assign/unassign endpoints when onboarding or offboarding multiple users to reduce API calls and improve performance.