Student Zones
Student Zones are personal isolated areas for each participant with privacy controls and permission enforcement. This is a unique feature not available in competing platforms like Miro, Figma, or Concept - specifically designed for EdTech use cases.
What Are Student Zones?
A Student Zone is a frame (rectangular area) assigned to a specific participant or user, where only the zone owner and host can edit content inside. Think of it as a personal desk in a classroom - each student works in their own space while the teacher monitors all progress.
Key Difference: Zones vs Frames
| Concept | Description | Assignment | Permissions |
|---|---|---|---|
| Frame | Rectangular area for organization | None (public) | Anyone can edit inside |
| Zone | Frame + participant assignment | Assigned to user/participant | Only owner + host can edit |
In technical terms: A zone is a frame with assigned_participant_id or assigned_user_id set.
Visual Concept
┌──────────────────────────────────────────────────────────────┐
│ EXAM BOARD (Teacher View) │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Alice's │ │ Bob's │ │ Charlie's │ │
│ │ Zone 🔒 │ │ Zone 🔒 │ │ Zone 🔒 │ │
│ │ │ │ │ │ │ │
│ │ [Drawing] │ │ [Drawing] │ │ [Drawing] │ │
│ │ [Objects] │ │ [Objects] │ │ [Objects] │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ Teacher sees all zones, students see only their own │
└──────────────────────────────────────────────────────────────┘Why Student Zones?
The Problem
Traditional whiteboards treat all participants equally - anyone can edit anywhere. This creates issues for educational scenarios:
- Exams: Students can see each other's answers
- Individual assignments: No way to isolate student work
- Assessment: Difficult to compare student work side-by-side
- Cheating: No technical enforcement of "stay in your area"
The Solution
Student Zones provide technical enforcement of personal workspaces:
- Permission model: Only zone owner can edit inside (enforced server-side)
- Privacy modes: Hide other students' work during exams
- Teacher oversight: Host sees all zones regardless of privacy mode
- Violation detection: Real-time alerts when someone tries to edit in another's zone
Competitive Advantage
| Platform | Personal Zones | Permission Enforcement | Privacy Modes |
|---|---|---|---|
| BoardAPI | ✅ Yes | ✅ Server-side | ✅ 3 modes |
| Miro | ❌ No | - | - |
| Figma/FigJam | ❌ No | - | - |
| VK Boards | ❌ No | - | - |
| Concept (Yandex) | ❌ No | - | - |
We're the only platform in the EdTech space with this feature.
Key Concepts
Zone Assignment
Zones can be assigned to two types of identifiers:
| ID Type | Description | Persistence | Use Case |
|---|---|---|---|
| Participant ID | Session-based UUID | Lost on reconnect | Anonymous participants, no login |
| User ID | Authenticated user UUID | Persistent across sessions | Registered students, LMS integration |
Priority: If both IDs are provided, assigned_user_id takes precedence.
Zone Visibility Modes
Zones support three privacy levels:
| Mode | Behavior | Teacher View | Student View | Use Case |
|---|---|---|---|---|
| visible | Everyone sees all zones (read-only for non-owners) | Sees all zones | Sees all zones (can only edit own) | Practice sessions, teacher reviews |
| hidden | Students see only their zone, host sees all | Sees all zones | Sees only own zone | Exams, tests, independent work |
| host_only | Only host sees zone (draft mode) | Sees all zones | Cannot see any zones | Preparing zones before class |
Visibility Examples
Visible Mode (Practice):
┌──────────────────────────────────────────────┐
│ Alice's View: │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ My Zone │ │ Bob's │ │Charlie's│ │
│ │ (editable│ │ (view │ │ (view │ │
│ │ ✏️) │ │ only) │ │ only) │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Alice can see everyone's work, │
│ but can only edit in her own zone │
└──────────────────────────────────────────────┘Hidden Mode (Exam):
┌──────────────────────────────────────────────┐
│ Alice's View: │
│ ┌─────────┐ │
│ │ My Zone │ 🚫 Cannot see Bob's/Charlie's │
│ │ (editable│ │
│ │ ✏️) │ │
│ └─────────┘ │
│ │
│ Teacher View: │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Alice's │ │ Bob's │ │Charlie's│ │
│ │ Zone │ │ Zone │ │ Zone │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Teacher sees all, students see only own │
└──────────────────────────────────────────────┘Permission Model
┌────────────────────────────────────────────┐
│ Zone Permission Hierarchy │
├────────────────────────────────────────────┤
│ │
│ Host (Teacher): │
│ ✅ Can view ALL zones │
│ ✅ Can edit ALL zones │
│ ✅ Can move/resize/delete zones │
│ │
│ Assigned Participant/User (Student): │
│ ✅ Can view their OWN zone │
│ ✅ Can edit ONLY in their zone │
│ ❌ Cannot move/resize zone itself │
│ ❌ Cannot edit in other zones │
│ │
│ Other Participants: │
│ ✅ Can view zones (if visible mode) │
│ ❌ Cannot edit in any zone │
│ ❌ Cannot view zones (if hidden mode) │
│ │
└────────────────────────────────────────────┘Zone Version
Zones use versioning to prevent race conditions when boundaries change:
zone_versionincrements when zone bounds, assignment, or visibility changes- Clients pass version when creating/updating objects
- If version mismatch, request rejected with
zoneChanged: true - Client refetches zone and retries operation
Use Cases
1. Online Exams
Create isolated exam zones where students cannot see each other's work.
// Create hidden zones for 3 students
const students = [
{ name: 'Alice', participantId: 'part-uuid-1' },
{ name: 'Bob', participantId: 'part-uuid-2' },
{ name: 'Charlie', participantId: 'part-uuid-3' }
];
for (let i = 0; i < students.length; i++) {
await fetch('https://api.boardapi.io/api/v1/boards/board-uuid/frames', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: `${students[i].name} - Exam Zone`,
bounds_x: 1300 * i,
bounds_y: 0,
bounds_width: 1200,
bounds_height: 800,
assigned_participant_id: students[i].participantId,
zone_visibility: 'hidden', // Students can't see each other
lock_mode: 'hard' // Can't navigate outside
})
});
}Result:
- Alice sees only her zone
- Bob sees only his zone
- Charlie sees only his zone
- Teacher sees all three zones for monitoring
- Hard lock prevents navigation to other zones
2. Practice Sessions (Visible Zones)
Students work independently but can see each other's progress for learning.
// Create visible zones for practice
for (let i = 0; i < students.length; i++) {
await fetch('/api/v1/boards/board-uuid/frames', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: `${students[i].name} - Practice`,
bounds_x: 1300 * i,
bounds_y: 0,
bounds_width: 1200,
bounds_height: 800,
assigned_participant_id: students[i].participantId,
zone_visibility: 'visible', // Everyone sees everyone
lock_mode: 'soft' // Can navigate to see others
})
});
}Result:
- Alice can see Bob's and Charlie's zones (read-only)
- Bob can see Alice's and Charlie's zones (read-only)
- Each student can only edit in their own zone
- Teacher can edit in all zones (for corrections)
3. Assessment Comparison
Teacher views all student work side-by-side for grading.
// After exam, switch to visible mode for grading
const zones = await fetch(
'/api/v1/boards/board-uuid/frames?assigned=true',
{
headers: { 'X-API-Key': 'your-api-key' }
}
).then(r => r.json());
for (const zone of zones) {
await fetch(`/api/v1/boards/board-uuid/frames/${zone.id}`, {
method: 'PATCH',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
zone_visibility: 'visible', // Show all zones for review
lock_mode: 'none' // Remove navigation locks
})
});
}Teacher View:
┌───────────┬───────────┬───────────┬───────────┐
│ Alice's │ Bob's │ Charlie's │ Dana's │
│ Answer │ Answer │ Answer │ Answer │
│ │ │ │ │
│ ✅ Correct│ ❌ Error │ ✅ Correct│ ⚠️ Partial│
│ │ │ │ │
│ Score: 10 │ Score: 7 │ Score: 10 │ Score: 8 │
└───────────┴───────────┴───────────┴───────────┘4. Group Workspaces with Privacy
Multiple students assigned to one zone for group work.
// Create group zone with multiple assignments
const groupZone = await fetch('/api/v1/boards/board-uuid/frames', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Blue Team Workspace',
bounds_x: 0,
bounds_y: 0,
bounds_width: 1400,
bounds_height: 1000,
zone_visibility: 'hidden', // Other groups can't see
lock_mode: 'soft',
background_color: '#E3F2FD'
})
}).then(r => r.json());
// Assign multiple students (bulk assign)
await fetch('/api/v1/boards/board-uuid/zones/bulk-assign', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
assignments: [
{ frame_id: groupZone.id, participant_id: 'student-1' },
{ frame_id: groupZone.id, participant_id: 'student-2' },
{ frame_id: groupZone.id, participant_id: 'student-3' }
]
})
});Note: Current API supports one assignment per zone. Group zones are on the roadmap (EPIC-046).
API Reference
Create Zone
POST /api/v1/boards/{boardUuid}/frames
Content-Type: application/json
X-API-Key: your-api-key
{
"name": "Alice's Zone",
"bounds_x": 0,
"bounds_y": 0,
"bounds_width": 1200,
"bounds_height": 800,
"assigned_participant_id": "part-uuid-123",
"zone_visibility": "hidden",
"lock_mode": "hard"
}Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"board_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Alice's Zone",
"bounds_x": 0,
"bounds_y": 0,
"bounds_width": 1200,
"bounds_height": 800,
"assigned_participant_id": "part-uuid-123",
"assigned_user_id": null,
"zone_visibility": "hidden",
"zone_version": 1,
"lock_mode": "hard",
"visibility": "active",
"created_at": "2025-11-26T10:00:00Z",
"updated_at": "2025-11-26T10:00:00Z"
}List Zones
GET /api/v1/boards/{boardUuid}/frames?assigned=true
X-API-Key: your-api-keyQuery parameter:
assigned=true- Filter for zones only (frames with assignments)
Response: Array of zone objects.
Update Zone Visibility
PATCH /api/v1/boards/{boardUuid}/frames/{frameId}
Content-Type: application/json
X-API-Key: your-api-key
{
"zone_visibility": "visible"
}Common updates:
- Switch between exam and practice mode (
hidden↔visible) - Prepare zones before class (
host_only→visible) - Resize zones (updates bounds, increments
zone_version)
Assign Participant to Zone
POST /api/v1/boards/{boardUuid}/frames/{frameId}/assign
Content-Type: application/json
X-API-Key: your-api-key
{
"participant_id": "part-uuid-123"
}Or assign authenticated user:
{
"user_id": "user-uuid-456"
}Bulk Assign
POST /api/v1/boards/{boardUuid}/zones/bulk-assign
Content-Type: application/json
X-API-Key: your-api-key
{
"assignments": [
{ "frame_id": "zone-1-uuid", "participant_id": "part-uuid-1" },
{ "frame_id": "zone-2-uuid", "participant_id": "part-uuid-2" },
{ "frame_id": "zone-3-uuid", "participant_id": "part-uuid-3" }
]
}Response:
{
"assigned": 3,
"failed": 0,
"results": [
{ "frame_id": "zone-1-uuid", "success": true, "zone_version": 2 },
{ "frame_id": "zone-2-uuid", "success": true, "zone_version": 1 },
{ "frame_id": "zone-3-uuid", "success": true, "zone_version": 1 }
]
}Limit: Max 50 assignments per request.
Delete Zone
DELETE /api/v1/boards/{boardUuid}/frames/{frameId}
X-API-Key: your-api-keyBehavior:
- Sets
visibilitytoarchived - Objects within zone are preserved (student work not lost)
- Assignment removed (
assigned_participant_idset to null) - Objects become "unassigned" and editable by everyone
WebSocket Events
Zone Assignment
// Server broadcasts when zone assigned
socket.on('zone:assigned', (data) => {
console.log(`Zone ${data.frameId} assigned to ${data.participantName}`);
// Update UI to show assignment
updateZoneIndicator(data.frameId, data.participantName);
});Payload:
{
"frameId": "550e8400-e29b-41d4-a716-446655440000",
"participantId": "part-uuid-123",
"participantName": "Alice",
"userId": null,
"zoneVersion": 2,
"timestamp": "2025-11-26T10:00:00Z"
}Visibility Changes
// Listen for visibility mode changes
socket.on('zone:visibility-changed', async (data) => {
if (data.visibility === 'hidden') {
// Exam mode activated
await filterBoardSnapshot(); // Hide other zones
console.log('Exam mode: Only your zone visible');
} else if (data.visibility === 'visible') {
// Practice mode activated
await refetchBoardSnapshot(); // Show all zones
console.log('Practice mode: All zones visible');
}
});Payload:
{
"frameId": "550e8400-e29b-41d4-a716-446655440000",
"visibility": "hidden",
"zoneVersion": 4,
"timestamp": "2025-11-26T10:10:00Z"
}Permission Violations
// Student tries to edit in another's zone
socket.on('zone:violation', (data) => {
console.warn(`Violation: ${data.reason}`);
// Revert change
undoLastAction(data.objectId);
// Show toast
showToast('You can only draw in your assigned zone', 'error');
});Payload:
{
"objectId": "obj-uuid-789",
"reason": "Cannot draw in another student's zone",
"operation": "create",
"timestamp": "2025-11-26T10:15:00Z"
}Zone Version Conflicts
// When creating object, pass zone version
socket.emit('object:create', {
...objectData,
zoneVersion: myZone.zone_version
});
// Handle version conflict
socket.on('object:create:result', async (result) => {
if (result.error && result.zoneChanged) {
// Zone boundaries changed, refetch
console.warn('Zone version outdated, refetching...');
myZone = await refetchMyZone();
// Retry with new version
retryObjectCreation(objectData, myZone.zone_version);
}
});Complete Example: Exam Setup
// Complete workflow for online exam setup
async function setupExamBoard(boardId, students, apiKey) {
// 1. Create hidden zones for each student
const zones = [];
for (let i = 0; i < students.length; i++) {
const zone = await fetch(
`https://api.boardapi.io/api/v1/boards/${boardId}/frames`,
{
method: 'POST',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: `${students[i].name} - Exam`,
bounds_x: 1300 * i,
bounds_y: 0,
bounds_width: 1200,
bounds_height: 800,
assigned_participant_id: students[i].participantId,
zone_visibility: 'hidden', // Isolation
lock_mode: 'hard', // No escaping
background_color: '#FFF3E0'
})
}
).then(r => r.json());
zones.push(zone);
}
console.log(`Created ${zones.length} exam zones`);
// 2. Navigate each student to their zone
for (let i = 0; i < students.length; i++) {
students[i].socket.emit('frame:navigate', {
boardId: boardId,
frameId: zones[i].id,
smooth: false // Instant navigation for exams
});
}
// 3. Setup violation monitoring
socket.on('zone:violation', (data) => {
console.warn(`Cheating attempt: ${data.participantName} tried to edit in ${data.zoneName}`);
sendNotification('TEACHER', `Alert: Possible cheating by ${data.participantName}`);
});
// 4. After exam, switch to visible for grading
async function endExamAndGrade() {
for (const zone of zones) {
await fetch(`/api/v1/boards/${boardId}/frames/${zone.id}`, {
method: 'PATCH',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
zone_visibility: 'visible', // Teacher can compare
lock_mode: 'none' // Remove locks
})
});
}
console.log('Exam ended. All zones visible for grading.');
}
return { zones, endExamAndGrade };
}
// Usage:
const students = [
{ name: 'Alice', participantId: 'part-1', socket: socket1 },
{ name: 'Bob', participantId: 'part-2', socket: socket2 },
{ name: 'Charlie', participantId: 'part-3', socket: socket3 }
];
const { zones, endExamAndGrade } = await setupExamBoard(
'board-uuid',
students,
'your-api-key'
);
// Later, after exam time expires:
await endExamAndGrade();Integration with Other Features
Zones + Frames
Zones are built on top of Frames, so all Frame features work:
- Clone zones for parallel groups (same assignment logic)
- Lock modes control viewport (soft/hard lock still applies)
- Background colors for visual organization
Zones + Presenter Modes
Spectate Mode with Zones:
// Teacher spectates student
socket.emit('spectate:start', {
boardId: 'board-uuid',
targetParticipantId: 'student-part-id',
stealthMode: true // Student unaware
});
// Teacher sees which zone student is in
socket.on('spectate:viewport', (data) => {
const zone = getZoneAtPosition(data.scrollX, data.scrollY);
if (zone && zone.assigned_participant_id === data.targetId) {
console.log(`Student is in their assigned zone: ${zone.name}`);
} else {
console.warn(`Student navigated outside their zone!`);
}
});Learn more about Presenter Modes →
Zones + Custom Components
Custom components respect zone permissions:
// Custom quiz component checks zone permissions
class QuizComponent {
async submitAnswer(answer) {
// Pass zone version for conflict detection
const result = await this.sdk.board.createObject({
type: 'quiz-answer',
data: { answer },
zoneVersion: this.currentZone.zone_version
});
if (result.error && result.zoneChanged) {
// Zone moved, refetch and retry
this.currentZone = await this.sdk.board.getMyZone();
return this.submitAnswer(answer);
}
}
}Learn more about Custom Components →
Best Practices
Zone Layout
Grid Layout (Exams):
[Zone 1] [Zone 2] [Zone 3] [Zone 4]
[Zone 5] [Zone 6] [Zone 7] [Zone 8]- Easy teacher navigation
- All zones visible on screen
- Use small zone size (1000x700)
Horizontal Strip (Practice):
[Zone 1] [Zone 2] [Zone 3] [Zone 4] [Zone 5]- Scroll left/right to see students
- Use larger zones (1200x900)
Naming Conventions
Good names:
- "Alice's Exam Zone"
- "Bob - Practice Area"
- "Group A Workspace"
Avoid:
- "Zone 1" (not personalized)
- "asldfj" (meaningless)
Performance
- Recommended: 25-30 zones per board (matches typical class size)
- Maximum: 50 zones (performance tested)
- Bulk operations: Use bulk-assign endpoint for >10 students
- Zone version: Always pass version when creating objects (prevents conflicts)
Privacy & Ethics
- Inform students: Disclose monitoring capabilities in syllabus
- Parental consent: For minors, get consent for zone monitoring
- Data retention: Archive zones after semester ends
- FERPA compliance: Use authenticated
user_id(not sessionparticipant_id)
Common Issues
Student can't edit in their zone
Check:
- Zone assignment is correct (GET zone to verify)
assigned_participant_idmatches current participant's ID- Participant hasn't reconnected (session ID changed)
- Zone is
active(notarchived)
Solution for reconnection:
// Detect reconnection
socket.on('connect', async () => {
const newParticipantId = await getMyParticipantId();
// Reassign zones
await fetch(`/api/v1/boards/${boardId}/frames/${zoneId}/assign`, {
method: 'POST',
headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify({ participant_id: newParticipantId })
});
});Zone version conflicts
Error: "zoneChanged": true
Cause: Zone boundaries changed (teacher moved/resized zone) while student was drawing.
Solution:
async function createObjectInZone(objectData, zone) {
try {
const result = await socket.emit('object:create', {
...objectData,
zoneVersion: zone.zone_version
});
if (result.error && result.zoneChanged) {
// Refetch zone
zone = await refetchMyZone();
// Retry with new version
return createObjectInZone(objectData, zone);
}
return result;
} catch (error) {
console.error('Object creation failed:', error);
}
}Zones overlapping
Problem: Zones created at same position, students can't see their zone.
Solution: Use consistent spacing:
const ZONE_WIDTH = 1200;
const ZONE_GAP = 100;
const ZONE_SPACING = ZONE_WIDTH + ZONE_GAP; // 1300
for (let i = 0; i < students.length; i++) {
bounds_x = ZONE_SPACING * i; // 0, 1300, 2600, ...
}Permission violations not detected
Check:
- Server-side zone middleware is enabled
- Client is sending object operations via WebSocket (not REST API)
- Object operations include
participantIdin payload - Zone assignments are loaded in server memory (check cache)
Quota & Limits
| Plan | Max Zones/Board | Bulk Assign Batch | Zone Assignments/Day |
|---|---|---|---|
| Starter | 10 | 10 | 100 |
| Professional | 30 | 30 | 500 |
| Business | 50 | 50 | 2,000 |
| Enterprise | 100 | 100 | Unlimited |
Note: These limits match typical class sizes (25-30 students).
Roadmap
Planned Features
- Group zones: Multiple students assigned to one zone (EPIC-046)
- Zone templates: Pre-built exam/practice zone layouts
- Automatic grading: AI analysis of zone content
- Zone exports: Export individual student work as PDF
- Parent portal: Parents view child's zone (read-only)
Next Steps
- Zones API Reference - Complete endpoint documentation
- Frames - Understanding zone foundation
- Presenter Modes - Monitor students in zones
- WebSocket API - Real-time zone events
Ready to use Student Zones? View Quick Start →