Skip to content

Zones API Reference

The Zones API provides endpoints for creating and managing personal areas for participants on boards. Student Zones enable isolated workspaces, exam mode with privacy controls, and group activities where each participant has their own drawing area. All zone operations require API key authentication.

Overview

Student Zones are personal rectangular areas on a board where only assigned participants can draw. This unique feature (not available in competing platforms like Miro) is designed specifically for EdTech use cases:

  • Online Exams: Each student works in isolation without seeing others' work
  • Practice Sessions: Individual workspaces while teacher monitors all progress
  • Assessment: Easy comparison of student work side-by-side

Key Concepts

ConceptDescription
ZoneFrame with assignment to specific participant/user
AssignmentParticipant or user ID who owns the zone
Zone VisibilityControls who can see zone content: visible, hidden, host_only
Zone VersionIncremental counter for race condition protection
PermissionsOnly zone owner and host can edit content inside

Zone Visibility Modes

ModeBehaviorUse Case
visibleEveryone sees all zones (read-only for non-owners)Practice, teacher reviews work
hiddenParticipants see only their zone, host sees allExams, tests, independent work
host_onlyOnly host sees zone (draft mode)Preparing zones before assignment

Permission Model

Zone Assignment:
├── Host: Can view and edit ALL zones
├── Assigned Participant/User: Can view and edit ONLY their zone
│   └── Cannot move/resize/delete the zone itself
└── Other Participants:
    ├── visible mode: Can view (read-only), cannot edit
    └── hidden mode: Cannot see or edit

Authentication

All Zones API endpoints require API key authentication:

bash
curl -H "X-API-Key: your-api-key" \
  https://api.boardapi.io/api/v1/boards/:boardUuid/zones

API keys are passed via the X-API-Key header. Get your API key from the Developer Dashboard.

Base URL

https://api.boardapi.io/api/v1

For development:

http://localhost:4000/api/v1

Endpoints Overview

MethodEndpointDescription
POST/boards/:boardUuid/framesCreate a new zone (frame with assignment)
GET/boards/:boardUuid/framesList all zones for a board
GET/boards/:boardUuid/frames/:frameIdGet a specific zone
PATCH/boards/:boardUuid/frames/:frameIdUpdate zone properties
DELETE/boards/:boardUuid/frames/:frameIdArchive a zone
POST/boards/:boardUuid/frames/:frameId/assignAssign participant to zone
POST/boards/:boardUuid/frames/:frameId/unassignRemove participant assignment
POST/boards/:boardUuid/zones/bulk-assignAssign multiple participants at once

Note: Zones are implemented as Frames with additional assignment fields. Use the Frames endpoints with zone-specific parameters.


Create Zone

Creates a new zone and optionally assigns it to a participant.

Request

http
POST /boards/:boardUuid/frames
X-API-Key: your-api-key
Content-Type: application/json

{
  "name": "Student 1 Zone",
  "bounds_x": 0,
  "bounds_y": 0,
  "bounds_width": 1200,
  "bounds_height": 800,
  "assigned_participant_id": "part-uuid-123",
  "zone_visibility": "hidden",
  "lock_mode": "soft"
}

URL Parameters

ParameterTypeDescription
boardUuidstringBoard UUID

Request Body Parameters

ParameterTypeRequiredDescription
namestringYesZone name (e.g., "Student 1", "Group A")
bounds_xnumberNoX coordinate in pixels (default: 0)
bounds_ynumberNoY coordinate in pixels (default: 0)
bounds_widthnumberNoWidth in pixels (default: 1200, min: 100)
bounds_heightnumberNoHeight in pixels (default: 800, min: 100)
assigned_participant_idstringNoParticipant UUID (session-based)
assigned_user_idstringNoUser UUID (authenticated, priority over participant_id)
zone_visibilitystringNoPrivacy mode: visible, hidden, host_only (default: visible)
lock_modestringNoViewport lock: none, soft, hard (default: none)
background_colorstringNoHex color code (e.g., #FFFFFF)

Important: Either assigned_participant_id or assigned_user_id must be provided to create a zone. If both are provided, assigned_user_id takes priority.

Response

Status Code: 201 Created

json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "board_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Student 1 Zone",
  "order": 0,
  "bounds_x": 0,
  "bounds_y": 0,
  "bounds_width": 1200,
  "bounds_height": 800,
  "lock_mode": "soft",
  "background_color": null,
  "visibility": "active",
  "assigned_participant_id": "part-uuid-123",
  "assigned_user_id": null,
  "zone_visibility": "hidden",
  "zone_version": 1,
  "created_at": "2025-11-26T10:00:00Z",
  "updated_at": "2025-11-26T10:00:00Z"
}

Response Fields

FieldTypeDescription
idstringZone UUID
board_idstringParent board UUID
namestringZone name
bounds_xnumberX coordinate
bounds_ynumberY coordinate
bounds_widthnumberWidth in pixels
bounds_heightnumberHeight in pixels
assigned_participant_idstringAssigned participant UUID or null
assigned_user_idstringAssigned user UUID or null
zone_visibilitystringPrivacy mode
zone_versionnumberVersion counter (increments on changes)
lock_modestringViewport control mode
visibilitystringFrame state (active, hidden, archived)
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 update timestamp

Example Requests

JavaScript/TypeScript:

javascript
// Create hidden zone for exam
const response = await fetch(
  'https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames',
  {
    method: 'POST',
    headers: {
      'X-API-Key': 'your-api-key',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'Student 1 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' // Prevent navigation outside zone
    })
  }
);

const zone = await response.json();
console.log(`Created zone: ${zone.name} (v${zone.zone_version})`);

cURL:

bash
curl -X POST https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Student 1 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"
  }'

PHP:

php
<?php
$ch = curl_init('https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames');

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'X-API-Key: your-api-key',
        'Content-Type: application/json'
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'name' => 'Student 1 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'
    ]),
    CURLOPT_RETURNTRANSFER => true
]);

$zone = json_decode(curl_exec($ch), true);
echo "Created zone: {$zone['name']}\n";

Use Cases

  • Create isolated exam zones for each student
  • Set up practice areas with visible zones
  • Prepare zones in host_only mode before class
  • Create group workspaces with privacy controls

Possible Errors

Status CodeErrorDescription
400Bad RequestInvalid zone data (missing assignment, width < 100, invalid zone_visibility)
404Not FoundBoard or participant not found
401UnauthorizedInvalid or missing API key
409ConflictZone overlaps with existing zone

List All Zones

Retrieves all zones for a board. Only returns frames with participant/user assignments.

Request

http
GET /boards/:boardUuid/frames?assigned=true
X-API-Key: your-api-key

URL Parameters

ParameterTypeDescription
boardUuidstringBoard UUID

Query Parameters

ParameterTypeRequiredDescription
assignedbooleanNoFilter for assigned frames only (zones)
includeArchivedbooleanNoInclude archived zones (default: false)

Response

Status Code: 200 OK

json
[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "board_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "Student 1 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": "soft",
    "visibility": "active",
    "created_at": "2025-11-26T10:00:00Z",
    "updated_at": "2025-11-26T10:00:00Z"
  },
  {
    "id": "660f9500-f3ac-52e5-b827-557766551111",
    "board_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "Student 2 Zone",
    "bounds_x": 1300,
    "bounds_y": 0,
    "bounds_width": 1200,
    "bounds_height": 800,
    "assigned_participant_id": "part-uuid-456",
    "assigned_user_id": null,
    "zone_visibility": "visible",
    "zone_version": 2,
    "lock_mode": "none",
    "visibility": "active",
    "created_at": "2025-11-26T10:05:00Z",
    "updated_at": "2025-11-26T10:10:00Z"
  }
]

Example Requests

JavaScript/TypeScript:

javascript
// Get all zones
const response = await fetch(
  'https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames?assigned=true',
  {
    headers: {
      'X-API-Key': 'your-api-key'
    }
  }
);

const zones = await response.json();
console.log(`Total zones: ${zones.length}`);

// Find zones by visibility mode
const hiddenZones = zones.filter(z => z.zone_visibility === 'hidden');
console.log(`Hidden zones (exam mode): ${hiddenZones.length}`);

cURL:

bash
# Get all zones
curl -H "X-API-Key: your-api-key" \
  'https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames?assigned=true'

PHP:

php
<?php
$headers = ['X-API-Key: your-api-key'];
$zones = json_decode(
  file_get_contents(
    'https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames?assigned=true',
    false,
    stream_context_create(['http' => ['header' => implode("\r\n", $headers)]])
  ),
  true
);

foreach ($zones as $zone) {
    echo "{$zone['name']} - Visibility: {$zone['zone_visibility']}\n";
}

Use Cases

  • Display teacher dashboard showing all student zones
  • Monitor exam progress
  • Check which students have assigned zones
  • Build zone navigation UI

Possible Errors

Status CodeErrorDescription
404Not FoundBoard does not exist
401UnauthorizedInvalid API key

Get Zone

Retrieves a specific zone by ID.

Request

http
GET /boards/:boardUuid/frames/:frameId
X-API-Key: your-api-key

URL Parameters

ParameterTypeDescription
boardUuidstringBoard UUID
frameIdstringZone (frame) UUID

Response

Status Code: 200 OK

json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "board_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Student 1 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": "soft",
  "visibility": "active",
  "created_at": "2025-11-26T10:00:00Z",
  "updated_at": "2025-11-26T10:00:00Z"
}

Example Requests

JavaScript/TypeScript:

javascript
const zoneId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(
  `https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/${zoneId}`,
  {
    headers: {
      'X-API-Key': 'your-api-key'
    }
  }
);

const zone = await response.json();
console.log(`Zone: ${zone.name} (Version: ${zone.zone_version})`);
console.log(`Assigned to: ${zone.assigned_participant_id || zone.assigned_user_id}`);

cURL:

bash
curl -H "X-API-Key: your-api-key" \
  'https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400-e29b-41d4-a716-446655440000'

Possible Errors

Status CodeErrorDescription
404Not FoundZone or board not found
401UnauthorizedInvalid API key

Update Zone

Updates zone properties such as visibility mode, bounds, or assignment.

Request

http
PATCH /boards/:boardUuid/frames/:frameId
X-API-Key: your-api-key
Content-Type: application/json

{
  "zone_visibility": "visible",
  "name": "Updated Zone Name"
}

URL Parameters

ParameterTypeDescription
boardUuidstringBoard UUID
frameIdstringZone UUID

Request Body Parameters

All fields are optional. Only provided fields will be updated.

ParameterTypeDescription
namestringNew zone name
bounds_xnumberNew X coordinate
bounds_ynumberNew Y coordinate
bounds_widthnumberNew width (min: 100)
bounds_heightnumberNew height (min: 100)
zone_visibilitystringNew privacy mode: visible, hidden, host_only
lock_modestringNew lock mode: none, soft, hard
background_colorstringNew hex color or null to remove

Note: Updating bounds increments zone_version to protect against race conditions.

Response

Status Code: 200 OK

json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "board_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Updated Zone Name",
  "bounds_x": 0,
  "bounds_y": 0,
  "bounds_width": 1200,
  "bounds_height": 800,
  "assigned_participant_id": "part-uuid-123",
  "assigned_user_id": null,
  "zone_visibility": "visible",
  "zone_version": 2,
  "lock_mode": "soft",
  "visibility": "active",
  "created_at": "2025-11-26T10:00:00Z",
  "updated_at": "2025-11-26T10:30:00Z"
}

Example Requests

JavaScript/TypeScript:

javascript
// Switch from exam mode to practice mode
const response = await fetch(
  'https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400',
  {
    method: 'PATCH',
    headers: {
      'X-API-Key': 'your-api-key',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      zone_visibility: 'visible', // Students can now see each other's work
      lock_mode: 'none' // Remove viewport lock
    })
  }
);

const updated = await response.json();
console.log(`Updated to v${updated.zone_version}`);

cURL:

bash
# Change to hidden mode (exam)
curl -X PATCH https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400 \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "zone_visibility": "hidden",
    "lock_mode": "hard"
  }'

# Resize zone
curl -X PATCH https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400 \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "bounds_width": 1600,
    "bounds_height": 900
  }'

PHP:

php
<?php
$ch = curl_init('https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400');

curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_HTTPHEADER => [
        'X-API-Key: your-api-key',
        'Content-Type: application/json'
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'zone_visibility' => 'visible',
        'lock_mode' => 'none'
    ]),
    CURLOPT_RETURNTRANSFER => true
]);

$updated = json_decode(curl_exec($ch), true);
echo "Updated: {$updated['name']} (v{$updated['zone_version']})\n";

Use Cases

  • Switch between exam and practice modes
  • Resize zones after content changes
  • Change zone names for clarity
  • Adjust zone positions (triggers sticky behavior - objects move with zone)

Sticky Behavior

When a host moves a zone (updates bounds_x or bounds_y), all objects inside the zone automatically move with it. This ensures student work stays within zone boundaries.

Possible Errors

Status CodeErrorDescription
400Bad RequestInvalid update data (e.g., width < 100, invalid zone_visibility)
404Not FoundZone or board not found
401UnauthorizedInvalid API key
403ForbiddenNon-host attempting to modify zone (only host can modify zones)
409ConflictNew bounds overlap with existing zone

Delete Zone

Soft deletes a zone by setting visibility to archived. Objects inside the zone are preserved.

Request

http
DELETE /boards/:boardUuid/frames/:frameId
X-API-Key: your-api-key

URL Parameters

ParameterTypeDescription
boardUuidstringBoard UUID
frameIdstringZone UUID

Response

Status Code: 200 OK

json
{
  "message": "Frame archived successfully"
}

Example Requests

JavaScript/TypeScript:

javascript
const response = await fetch(
  'https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400',
  {
    method: 'DELETE',
    headers: {
      'X-API-Key': 'your-api-key'
    }
  }
);

const result = await response.json();
console.log(result.message);

cURL:

bash
curl -X DELETE \
  -H "X-API-Key: your-api-key" \
  'https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400'

PHP:

php
<?php
$ch = curl_init('https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400');

curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => ['X-API-Key: your-api-key'],
    CURLOPT_RETURNTRANSFER => true
]);

$result = json_decode(curl_exec($ch), true);
echo $result['message'];

Behavior

  • Zone visibility changes to archived
  • Zone is hidden from zone lists (unless includeArchived=true)
  • Objects within zone are NOT deleted (student work is preserved)
  • Assignment is removed (assigned_participant_id and assigned_user_id set to null)
  • Objects become "unassigned" and editable by everyone

Possible Errors

Status CodeErrorDescription
404Not FoundZone or board not found
401UnauthorizedInvalid API key
403ForbiddenNon-host attempting to delete zone

Assign Participant

Assigns or reassigns a participant/user to an existing frame, converting it to a zone.

Request

http
POST /boards/:boardUuid/frames/:frameId/assign
X-API-Key: your-api-key
Content-Type: application/json

{
  "participant_id": "part-uuid-123"
}

Or assign authenticated user:

http
POST /boards/:boardUuid/frames/:frameId/assign
X-API-Key: your-api-key
Content-Type: application/json

{
  "user_id": "user-uuid-456"
}

URL Parameters

ParameterTypeDescription
boardUuidstringBoard UUID
frameIdstringFrame UUID

Request Body Parameters

ParameterTypeRequiredDescription
participant_idstringNo*Participant UUID (session-based)
user_idstringNo*User UUID (authenticated, priority over participant_id)

Note: Either participant_id or user_id must be provided. If both are provided, user_id takes priority.

Response

Status Code: 200 OK

json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "board_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Zone 1",
  "bounds_x": 0,
  "bounds_y": 0,
  "bounds_width": 1200,
  "bounds_height": 800,
  "assigned_participant_id": "part-uuid-123",
  "assigned_user_id": null,
  "zone_visibility": "visible",
  "zone_version": 2,
  "lock_mode": "none",
  "visibility": "active",
  "created_at": "2025-11-26T10:00:00Z",
  "updated_at": "2025-11-26T10:35:00Z"
}

Example Requests

JavaScript/TypeScript:

javascript
// Assign participant to zone
const response = await fetch(
  'https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400/assign',
  {
    method: 'POST',
    headers: {
      'X-API-Key': 'your-api-key',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      participant_id: 'part-uuid-123'
    })
  }
);

const zone = await response.json();
console.log(`Assigned to: ${zone.assigned_participant_id}`);

cURL:

bash
# Assign participant
curl -X POST https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400/assign \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"participant_id": "part-uuid-123"}'

# Assign authenticated user
curl -X POST https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400/assign \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "user-uuid-456"}'

PHP:

php
<?php
$ch = curl_init('https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400/assign');

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'X-API-Key: your-api-key',
        'Content-Type: application/json'
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'participant_id' => 'part-uuid-123'
    ]),
    CURLOPT_RETURNTRANSFER => true
]);

$zone = json_decode(curl_exec($ch), true);
echo "Assigned to: {$zone['assigned_participant_id']}\n";

Use Cases

  • Assign student to pre-created zone
  • Reassign zone to different participant
  • Convert regular frame to student zone
  • Handle participant reconnection with new session ID

Possible Errors

Status CodeErrorDescription
400Bad RequestMissing both participant_id and user_id
404Not FoundFrame, board, or participant not found
401UnauthorizedInvalid API key
403ForbiddenNon-host attempting to assign

Unassign Participant

Removes participant/user assignment from a zone, converting it back to a regular frame.

Request

http
POST /boards/:boardUuid/frames/:frameId/unassign
X-API-Key: your-api-key

URL Parameters

ParameterTypeDescription
boardUuidstringBoard UUID
frameIdstringZone UUID

Response

Status Code: 200 OK

json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "board_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Zone 1",
  "bounds_x": 0,
  "bounds_y": 0,
  "bounds_width": 1200,
  "bounds_height": 800,
  "assigned_participant_id": null,
  "assigned_user_id": null,
  "zone_visibility": "visible",
  "zone_version": 3,
  "lock_mode": "none",
  "visibility": "active",
  "created_at": "2025-11-26T10:00:00Z",
  "updated_at": "2025-11-26T10:40:00Z"
}

Example Requests

JavaScript/TypeScript:

javascript
const response = await fetch(
  'https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400/unassign',
  {
    method: 'POST',
    headers: {
      'X-API-Key': 'your-api-key'
    }
  }
);

const frame = await response.json();
console.log('Zone unassigned, now a regular frame');

cURL:

bash
curl -X POST \
  -H "X-API-Key: your-api-key" \
  'https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400/unassign'

PHP:

php
<?php
$ch = curl_init('https://api.boardapi.io/api/v1/boards/a1b2c3d4/frames/550e8400/unassign');

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['X-API-Key: your-api-key'],
    CURLOPT_RETURNTRANSFER => true
]);

$frame = json_decode(curl_exec($ch), true);
echo "Unassigned zone\n";

Behavior

  • Sets assigned_participant_id and assigned_user_id to null
  • Objects within zone remain (not deleted)
  • Objects become editable by all participants
  • Zone version increments

Use Cases

  • Remove student from zone
  • Reuse zone for different participant
  • Clean up after class session

Possible Errors

Status CodeErrorDescription
404Not FoundZone or board not found
401UnauthorizedInvalid API key
403ForbiddenNon-host attempting to unassign

Bulk Assign

Assigns multiple participants to zones in a single request. Useful for quickly setting up class sessions.

Request

http
POST /boards/:boardUuid/zones/bulk-assign
X-API-Key: your-api-key
Content-Type: application/json

{
  "assignments": [
    {
      "frame_id": "550e8400-e29b-41d4-a716-446655440000",
      "participant_id": "part-uuid-123"
    },
    {
      "frame_id": "660f9500-f3ac-52e5-b827-557766551111",
      "user_id": "user-uuid-456"
    }
  ]
}

URL Parameters

ParameterTypeDescription
boardUuidstringBoard UUID

Request Body Parameters

ParameterTypeRequiredDescription
assignmentsarrayYesArray of assignment objects (max 50)
assignments[].frame_idstringYesFrame UUID to assign
assignments[].participant_idstringNo*Participant UUID
assignments[].user_idstringNo*User UUID (priority)

Note: Each assignment must have either participant_id or user_id.

Response

Status Code: 200 OK

json
{
  "assigned": 2,
  "failed": 0,
  "results": [
    {
      "frame_id": "550e8400-e29b-41d4-a716-446655440000",
      "success": true,
      "zone_version": 2
    },
    {
      "frame_id": "660f9500-f3ac-52e5-b827-557766551111",
      "success": true,
      "zone_version": 1
    }
  ]
}

Example Requests

JavaScript/TypeScript:

javascript
// Assign 10 students to 10 zones at once
const zones = ['zone-1-uuid', 'zone-2-uuid', /* ... */];
const participants = ['part-1-uuid', 'part-2-uuid', /* ... */];

const assignments = zones.map((frameId, i) => ({
  frame_id: frameId,
  participant_id: participants[i]
}));

const response = await fetch(
  'https://api.boardapi.io/api/v1/boards/a1b2c3d4/zones/bulk-assign',
  {
    method: 'POST',
    headers: {
      'X-API-Key': 'your-api-key',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ assignments })
  }
);

const result = await response.json();
console.log(`Assigned: ${result.assigned}, Failed: ${result.failed}`);

cURL:

bash
curl -X POST https://api.boardapi.io/api/v1/boards/a1b2c3d4/zones/bulk-assign \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "assignments": [
      {
        "frame_id": "550e8400-e29b-41d4-a716-446655440000",
        "participant_id": "part-uuid-123"
      },
      {
        "frame_id": "660f9500-f3ac-52e5-b827-557766551111",
        "participant_id": "part-uuid-456"
      }
    ]
  }'

PHP:

php
<?php
$zones = ['550e8400', '660f9500', /* ... */];
$participants = ['part-uuid-123', 'part-uuid-456', /* ... */];

$assignments = array_map(function($frameId, $participantId) {
    return [
        'frame_id' => $frameId,
        'participant_id' => $participantId
    ];
}, $zones, $participants);

$ch = curl_init('https://api.boardapi.io/api/v1/boards/a1b2c3d4/zones/bulk-assign');

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'X-API-Key: your-api-key',
        'Content-Type: application/json'
    ],
    CURLOPT_POSTFIELDS => json_encode(['assignments' => $assignments]),
    CURLOPT_RETURNTRANSFER => true
]);

$result = json_decode(curl_exec($ch), true);
echo "Assigned: {$result['assigned']}, Failed: {$result['failed']}\n";

Use Cases

  • Quick class setup (assign all students at once)
  • Reset zones after session
  • Batch reassignment after roster changes

Performance Limit

Maximum 50 assignments per request. For larger batches, make multiple requests.

Possible Errors

Status CodeErrorDescription
400Bad RequestInvalid assignments array, exceeds 50 items, or missing IDs
404Not FoundBoard not found
401UnauthorizedInvalid API key
403ForbiddenNon-host attempting bulk assign

WebSocket Events

Zones support real-time synchronization via WebSocket. See WebSocket API for connection details.

Zone Events

EventDirectionDescription
zone:assignedServer → ClientsParticipant assigned to zone
zone:unassignedServer → ClientsAssignment removed
zone:visibility-changedServer → ClientsPrivacy mode changed
zone:violationServer → ClientParticipant tried to edit outside their zone
zone:cache-invalidateServer → ClientsForce clients to refetch zones
zone:bounds-changedServer → ClientsZone moved/resized (sticky behavior)

Event Payloads

zone:assigned

javascript
{
  "frameId": "550e8400-e29b-41d4-a716-446655440000",
  "participantId": "part-uuid-123",
  "participantName": "John Doe",
  "userId": null,
  "zoneVersion": 2,
  "timestamp": "2025-11-26T10:00:00Z"
}

zone:unassigned

javascript
{
  "frameId": "550e8400-e29b-41d4-a716-446655440000",
  "previousParticipantId": "part-uuid-123",
  "previousUserId": null,
  "zoneVersion": 3,
  "timestamp": "2025-11-26T10:05:00Z"
}

zone:visibility-changed

javascript
{
  "frameId": "550e8400-e29b-41d4-a716-446655440000",
  "visibility": "hidden", // 'visible' | 'hidden' | 'host_only'
  "zoneVersion": 4,
  "timestamp": "2025-11-26T10:10:00Z"
}

zone:violation

javascript
{
  "objectId": "obj-uuid-789",
  "reason": "Cannot draw in another student's zone",
  "operation": "create", // 'create' | 'update' | 'delete'
  "timestamp": "2025-11-26T10:15:00Z"
}

zone:cache-invalidate

javascript
{
  "boardUuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "reason": "Zone assignments modified",
  "timestamp": "2025-11-26T10:20:00Z"
}

zone:bounds-changed

javascript
{
  "frameId": "550e8400-e29b-41d4-a716-446655440000",
  "bounds": {
    "x": 100,
    "y": 50,
    "width": 1200,
    "height": 800
  },
  "zoneVersion": 5,
  "timestamp": "2025-11-26T10:25:00Z"
}

Client-Side Handling

Listen for violations:

javascript
socket.on('zone:violation', (data) => {
  console.warn(`Violation: ${data.reason}`);
  // Revert object to original position
  undoLastAction(data.objectId);
  // Show toast notification
  showToast(`You can only draw in your assigned zone`, 'error');
});

Listen for visibility changes:

javascript
socket.on('zone:visibility-changed', async (data) => {
  if (data.visibility === 'hidden') {
    // Exam mode activated - hide other students' work
    await filterBoardSnapshot();
    console.log('Exam mode: You can only see your zone');
  } else {
    // Practice mode - show all zones
    await refetchBoardSnapshot();
    console.log('Practice mode: All zones visible');
  }
});

Handle zone version conflicts:

javascript
// When creating/updating object, pass current zone version
const result = await socket.emit('object:create', {
  ...objectData,
  zoneVersion: myZone.zone_version
});

if (result.error && result.zoneChanged) {
  // Zone boundaries changed, refetch and retry
  myZone = await refetchMyZone();
  console.log(`Zone version updated to v${myZone.zone_version}`);
  // Retry operation
}

React to cache invalidation:

javascript
socket.on('zone:cache-invalidate', async () => {
  // Refetch zones from server
  const zones = await fetch('/boards/a1b2c3d4/frames?assigned=true')
    .then(r => r.json());
  updateZonesCache(zones);
});

Response Status Codes

CodeMeaningDescription
200OKRequest succeeded
201CreatedZone created successfully
400Bad RequestInvalid zone data, missing assignment, or invalid visibility mode
401UnauthorizedMissing or invalid API key
403ForbiddenNon-host attempting protected operation
404Not FoundZone, board, or participant not found
409ConflictZone overlap detected or version conflict
500Internal Server ErrorServer error occurred

Common Use Cases

Setup Exam Mode

javascript
// Complete exam setup workflow
async function setupExamMode(boardId, students) {
  const zones = [];

  // 1. Create hidden zones for each student
  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': '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' // Prevent navigation outside zone
        })
      }
    ).then(r => r.json());

    zones.push(zone);
  }

  console.log(`Created ${zones.length} exam zones`);
  return zones;
}

// Usage
const students = [
  { name: 'Alice', participantId: 'part-uuid-1' },
  { name: 'Bob', participantId: 'part-uuid-2' },
  { name: 'Charlie', participantId: 'part-uuid-3' }
];

await setupExamMode('a1b2c3d4', students);

Switch from Exam to Practice Mode

javascript
// Change all zones from hidden to visible
async function switchToPracticeMode(boardId) {
  // Get all zones
  const zones = await fetch(
    `https://api.boardapi.io/api/v1/boards/${boardId}/frames?assigned=true`,
    {
      headers: { 'X-API-Key': 'your-api-key' }
    }
  ).then(r => r.json());

  // Update each zone
  for (const zone of zones) {
    await fetch(
      `https://api.boardapi.io/api/v1/boards/${boardId}/frames/${zone.id}`,
      {
        method: 'PATCH',
        headers: {
          'X-API-Key': 'your-api-key',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          zone_visibility: 'visible',
          lock_mode: 'none'
        })
      }
    );
  }

  console.log(`Switched ${zones.length} zones to practice mode`);
}

Create Group Workspaces

javascript
// Create zones for group collaboration
async function createGroupZones(boardId, groups) {
  const zones = [];

  for (let i = 0; i < groups.length; i++) {
    const zone = await fetch(
      `https://api.boardapi.io/api/v1/boards/${boardId}/frames`,
      {
        method: 'POST',
        headers: {
          'X-API-Key': 'your-api-key',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: groups[i].name,
          bounds_x: 1400 * i,
          bounds_y: 0,
          bounds_width: 1300,
          bounds_height: 1000,
          zone_visibility: 'visible', // Groups can see each other's work
          background_color: groups[i].color
        })
      }
    ).then(r => r.json());

    zones.push(zone);

    // Assign all group members
    for (const member of groups[i].members) {
      await fetch(
        `https://api.boardapi.io/api/v1/boards/${boardId}/frames/${zone.id}/assign`,
        {
          method: 'POST',
          headers: {
            'X-API-Key': 'your-api-key',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            participant_id: member.participantId
          })
        }
      );
    }
  }

  return zones;
}

// Usage
const groups = [
  {
    name: 'Blue Team',
    color: '#E3F2FD',
    members: [
      { participantId: 'part-uuid-1' },
      { participantId: 'part-uuid-2' }
    ]
  },
  {
    name: 'Red Team',
    color: '#FFEBEE',
    members: [
      { participantId: 'part-uuid-3' },
      { participantId: 'part-uuid-4' }
    ]
  }
];

await createGroupZones('a1b2c3d4', groups);

Monitor Student Progress

javascript
// Real-time teacher dashboard
async function monitorStudentZones(boardId) {
  const zones = await fetch(
    `https://api.boardapi.io/api/v1/boards/${boardId}/frames?assigned=true`,
    {
      headers: { 'X-API-Key': 'your-api-key' }
    }
  ).then(r => r.json());

  // Get board snapshot with all student work
  const board = await fetch(
    `https://api.boardapi.io/api/v1/boards/${boardId}`,
    {
      headers: { 'X-API-Key': 'your-api-key' }
    }
  ).then(r => r.json());

  // Analyze each zone
  const progress = zones.map(zone => {
    const objectsInZone = board.state.objects.filter(obj =>
      isInsideZone(obj.bounds, zone.bounds)
    );

    return {
      student: zone.name,
      objectCount: objectsInZone.length,
      isEmpty: objectsInZone.length === 0,
      version: zone.zone_version
    };
  });

  console.table(progress);
  return progress;
}

function isInsideZone(objBounds, zoneBounds) {
  return (
    objBounds.x >= zoneBounds.x &&
    objBounds.x + objBounds.width <= zoneBounds.x + zoneBounds.width &&
    objBounds.y >= zoneBounds.y &&
    objBounds.y + objBounds.height <= zoneBounds.y + zoneBounds.height
  );
}

Race Condition Protection

Zones use versioning to prevent concurrent modification conflicts:

Client-Side Version Tracking

javascript
// Track zone version on client
let myZone = {
  id: '550e8400-e29b-41d4-a716-446655440000',
  zone_version: 1,
  bounds: { x: 0, y: 0, width: 1200, height: 800 }
};

// Pass version when creating objects
socket.emit('object:create', {
  ...objectData,
  zoneVersion: myZone.zone_version
});

// Handle version conflicts
socket.on('object:create:result', (result) => {
  if (result.error && result.zoneChanged) {
    // Zone was modified (host moved it), refetch
    console.warn('Zone boundaries changed, refetching...');
    refetchMyZone().then(updatedZone => {
      myZone = updatedZone;
      // Retry operation with new version
    });
  }
});

Server-Side Version Check

The server automatically increments zone_version when:

  • Zone bounds change (host moves/resizes zone)
  • Participant assignment changes
  • Visibility mode changes

If client provides outdated version, request is rejected with zoneChanged: true.

Performance Limits

ParameterLimitNote
Zones per board50Performance optimized for this limit
Objects per zone100 (soft limit)Warning shown, not enforced
Bulk assignments50 per requestFor larger batches, make multiple requests
Concurrent students50WebSocket capacity

Known Limitations

Mobile/Touch

  • Pinch-to-zoom may move objects outside zone boundaries
  • Recommended to use desktop for exam mode

Reconnection

  • Participant ID (session-based): Lost on reconnect, requires reassignment
  • User ID (authenticated): Preserved across sessions

Export/Import

  • Zone assignments are removed when board is exported/imported
  • Objects are preserved, but become unassigned

Architecture

For detailed technical architecture including permission checks, snapshot filtering, and WebSocket integration, see:

Next Steps