Appearance
Levels, Areas, Markers
Manage map entities through the project API. All endpoints require authentication.
How It Works
Levels, areas, and markers are nested inside a project. You can:
- Read them via
GET /api/projects/{project_id}/levels/ - Create / Update / Delete them via
PATCH /api/projects/{project_id}/withlevels_data
This is the same approach the Layota editor uses internally.
Get Levels (with Areas & Markers)
http
GET /api/projects/{project_id}/levels/
Authorization: Bearer <token>Response:
json
[
{
"id": "level-uuid",
"name": "Floor 1",
"key": "floor-1",
"order": 0,
"isHidden": false,
"imageUrl": "https://...",
"imageThumbnails": ["https://...small", "https://...medium"],
"defaultZoom": 1.0,
"minZoom": 0.75,
"maxZoom": 4.0,
"backgroundColor": null,
"areas": [
{
"id": "area-uuid",
"key": "coffee-shop",
"data": [[100, 200], [300, 200], [300, 400], [100, 400]],
"color": "#FF5733",
"fillOpacity": 0.3,
"strokeWidth": 2,
"hoverOpacity": 0.5,
"zoomOnSelect": true,
"status": null,
"address": null,
"workingHours": null,
"email": null,
"webpage": null,
"telephone": null,
"rating": null,
"price": null,
"tags": [
{ "id": "tag-uuid-1", "name": "Food", "color": "#FF5733" }
],
"images": [
{
"id": "img-uuid",
"url": "https://...",
"thumbnails": ["https://...small", "https://...medium"],
"order": 0
}
],
"translations": {
"en": {
"title": "Coffee Shop",
"description": "Best coffee in town",
"statusLabel": "",
"features": []
},
"ru": {
"title": "Кофейня",
"description": "Лучший кофе",
"statusLabel": null,
"features": []
}
}
}
],
"markers": [
{
"id": "marker-uuid",
"key": "main-entrance",
"position": { "x": 250, "y": 100 },
"type": "classic",
"icon": "door-open",
"customIcon": null,
"color": "#00AA00",
"size": 10,
"opacity": 1,
"pinStyle": "icon",
"markerNumber": 1,
"showTooltip": true,
"showCard": true,
"zoomOnSelect": true,
"status": null,
"tags": [],
"images": [],
"translations": {
"en": {
"title": "Main Entrance",
"description": null,
"statusLabel": "",
"features": []
}
}
}
]
}
]Where is title?
Responses have no top-level title on areas and markers — the display name always comes from translations (the project's default language entry is always present). On write you send a top-level title, which becomes the default-language title.
Public Read-Only Endpoints
These endpoints require no authentication and work for published projects. They also support API key auth (Authorization: Bearer sk_... — read or write key), which bypasses the allowed domains check.
Lookup by UUID or by slug key (e.g. floor-1, coffee-shop).
List Levels (metadata only)
http
GET /api/p/{project_id}/levels/Returns a lightweight list without nested areas/markers:
json
[
{
"id": "level-uuid",
"name": "Floor 1",
"key": "floor-1",
"imageUrl": "https://...",
"imageThumbnails": ["https://..."],
"isHidden": false,
"defaultZoom": 1.0,
"minZoom": 0.75,
"maxZoom": 4.0,
"backgroundColor": null,
"order": 0,
"areaCount": 12,
"markerCount": 5
}
]Get Level Detail
http
GET /api/p/{project_id}/levels/{level_id_or_key}/Returns full level with all areas and markers (same format as GET /api/projects/{id}/levels/ but for a single level).
Get Area Detail
http
GET /api/p/{project_id}/areas/{area_id_or_key}/Returns a single area with images, tags, and translations.
Get Marker Detail
http
GET /api/p/{project_id}/markers/{marker_id_or_key}/Returns a single marker with images, tags, and translations.
Update Project (with Levels, Areas, Markers)
Send levels_data as part of a project update. Each level can contain areas and markers arrays. Include id for existing entities (to update) or omit it (to create). Entities not included in the array are deleted.
http
PATCH /api/projects/{project_id}/
Authorization: Bearer <token>
Content-Type: application/json
{
"levels_data": [
{
"id": "existing-level-uuid",
"name": "Ground Floor",
"key": "ground-floor",
"order": 0,
"isHidden": false,
"areas": [
{
"id": "existing-area-uuid",
"title": "Updated Coffee Shop",
"key": "coffee-shop",
"data": [[100, 200], [300, 200], [300, 400], [100, 400]],
"color": "#3366FF",
"fillOpacity": 0.3,
"strokeWidth": 2,
"tags": ["tag-uuid"]
},
{
"title": "New Store",
"key": "new-store",
"data": [[400, 200], [600, 200], [600, 400], [400, 400]],
"color": "#FF5733"
}
],
"markers": [
{
"title": "Main Entrance",
"key": "main-entrance",
"position": { "x": 250, "y": 100 },
"type": "classic",
"icon": "door-open",
"color": "#00AA00"
}
]
}
]
}With Image Upload
When uploading level images, use multipart/form-data with the data as a JSON string in the data field:
http
PATCH /api/projects/{project_id}/
Authorization: Bearer <token>
Content-Type: multipart/form-data
data={"levels_data": [...]}
level_image_0=<file>Marker Types
| Type | Description |
|---|---|
classic | Standard pin (default) |
pulse | Animated pulsing dot |
drop-pin | Map-style pin with configurable pin style |
rounded-square | Square pin with rounded corners |
numbered | Pin with a number inside |
label-hover | Text label on hover |
Area & Marker Fields Reference
Area Fields
| Field | Type | Description |
|---|---|---|
title | string | Display name (write-only at top level; read it from translations) |
key | string | URL-friendly slug |
data | array | Polygon coordinates [[x, y], ...] |
color | string | Hex color |
fillOpacity | number | Fill transparency (0–1) |
strokeWidth | number | Border thickness |
hoverOpacity | number | Opacity on hover |
zoomOnSelect | boolean | Zoom on click |
status | object | { "type": "...", "label": "..." } or null; type is one of success, warning, error, info. Responses contain only type — the label is returned per-language in translations.statusLabel |
address | string | Physical address |
workingHours | object | Schedule per day |
email | string | Email address |
webpage | string | Website URL |
telephone | string | Phone number |
rating | number | Rating (e.g. 0–5) |
price | string | Free-text price |
features | array | List of feature strings (write-only at top level; read per-language in translations.features) |
tags | array | Tag objects { "id", "name", "color" } in responses. On write, pass existing tag UUIDs (or objects with id); objects with name/color and no id create new tags |
Marker Fields
| Field | Type | Description |
|---|---|---|
title | string | Display name (write-only at top level; read it from translations) |
key | string | URL-friendly slug |
position | object | { "x": number, "y": number } |
type | string | Marker type (see table above) |
icon | string | Built-in icon name (Lucide) or custom icon URL |
customIcon | string | Custom icon UUID (set when using an uploaded icon) |
color | string | Hex color |
size | number | Size in pixels |
opacity | number | Opacity (0–1) |
pinStyle | string | icon, dot, plain (drop-pin only) |
markerNumber | number | Number for numbered type |
showTooltip | boolean | Show tooltip on hover |
showCard | boolean | Show info card on click |
zoomOnSelect | boolean | Zoom on click |
status | object | { "type": "...", "label": "..." } or null; type is one of success, warning, error, info. Responses contain only type — the label is returned per-language in translations.statusLabel |
address | string | Physical address |
workingHours | object | Schedule per day |
email | string | Email address |
webpage | string | Website URL |
telephone | string | Phone number |
rating | number | Rating (e.g. 0–5) |
price | string | Free-text price |
features | array | List of feature strings (write-only at top level; read per-language in translations.features) |
tags | array | Tag objects { "id", "name", "color" } in responses. On write, pass existing tag UUIDs (or objects with id); objects with name/color and no id create new tags |
Translations
Translations are included in the entity response and can be set via the project update:
json
{
"levels_data": [
{
"id": "level-uuid",
"areas": [
{
"id": "area-uuid",
"translations": {
"ru": {
"title": "Кофейня",
"description": "Лучший кофе",
"statusLabel": "Открыто",
"features": ["Wi-Fi", "Розетки"]
}
}
}
]
}
]
}Area Images
Upload Image
http
POST /api/projects/{project_id}/upload-image/
Authorization: Bearer <token>
Content-Type: multipart/form-data
image=<file>Images can also be uploaded as part of the project update using multipart/form-data.