Skip to content

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}/ with levels_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

TypeDescription
classicStandard pin (default)
pulseAnimated pulsing dot
drop-pinMap-style pin with configurable pin style
rounded-squareSquare pin with rounded corners
numberedPin with a number inside
label-hoverText label on hover

Area & Marker Fields Reference

Area Fields

FieldTypeDescription
titlestringDisplay name (write-only at top level; read it from translations)
keystringURL-friendly slug
dataarrayPolygon coordinates [[x, y], ...]
colorstringHex color
fillOpacitynumberFill transparency (0–1)
strokeWidthnumberBorder thickness
hoverOpacitynumberOpacity on hover
zoomOnSelectbooleanZoom on click
statusobject{ "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
addressstringPhysical address
workingHoursobjectSchedule per day
emailstringEmail address
webpagestringWebsite URL
telephonestringPhone number
ratingnumberRating (e.g. 0–5)
pricestringFree-text price
featuresarrayList of feature strings (write-only at top level; read per-language in translations.features)
tagsarrayTag 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

FieldTypeDescription
titlestringDisplay name (write-only at top level; read it from translations)
keystringURL-friendly slug
positionobject{ "x": number, "y": number }
typestringMarker type (see table above)
iconstringBuilt-in icon name (Lucide) or custom icon URL
customIconstringCustom icon UUID (set when using an uploaded icon)
colorstringHex color
sizenumberSize in pixels
opacitynumberOpacity (0–1)
pinStylestringicon, dot, plain (drop-pin only)
markerNumbernumberNumber for numbered type
showTooltipbooleanShow tooltip on hover
showCardbooleanShow info card on click
zoomOnSelectbooleanZoom on click
statusobject{ "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
addressstringPhysical address
workingHoursobjectSchedule per day
emailstringEmail address
webpagestringWebsite URL
telephonestringPhone number
ratingnumberRating (e.g. 0–5)
pricestringFree-text price
featuresarrayList of feature strings (write-only at top level; read per-language in translations.features)
tagsarrayTag 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.

Layota Documentation