VC Publisher API

Overview

Additionally to the UI, the VC Publisher provides a REST API.

The interactive API documentation (Swagger UI) is available at this URL:

https://Publisher-url.de/apidoc/v1/#

The Publisher-url.de needs to be replaced with the actual URL of your VC Publisher instance. All endpoints can be browsed and tested directly in the browser. When using the Swagger UI, click the "Try it out" button and fill in the required information for the specific endpoint.

Authentication

Every API endpoint except the login endpoints requires a valid Bearer token. The token is obtained by logging in and must be included in the Authorization header of every subsequent request:

Authorization: Bearer <your-token>

Without a valid token, the API responds with 401 Unauthorized.

Workflow 1: Login & Authentication

There are two login methods depending on the use case: API token login for programmatic access, and session-based login for UI access. For

API Token Login

Step 1 – Obtain a token

Send a POST request to /api/v1/login with username and password:

POST /api/v1/login
Content-Type: application/json

{
  "username": "your-username",
  "password": "your-password"
}

When using the login endpoint in the Swagger UI, the "algorithm": "sha-256" needs to be removed if the password is entered in plain text. A successful response returns 200 OK:

{
  "_id": "64abc...",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
  "tokenExpires": "2026-03-12T10:00:00.000Z"
}

The token field is the Bearer token. The tokenExpires field indicates when it will stop working.

Step 2 – Use the token

Include the token in the Authorization header of every request:

GET /api/v1/user/whoami
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6...

In the Swagger UI, click the "Authorize" button and enter the token to authenticate all requests made through the interface.


Logout

To invalidate the current session or token:

GET /api/v1/logout
Authorization: Bearer <token>

Workflow 2: Create a Project

Requires super user role.

Step 1 – Create the project

For creating a project the POST /api/v1/project endpoint needs to be used and a name, description, and bounding box needs to be provided.

POST /api/v1/project
Authorization: Bearer <token>
{
  "name": "My Example Project",
  "description": "A project for demonstrating the API",
  "bbox": [-180, -85, 180, 85]
}

The optional query parameter managerId assigns a specific user as the project manager. If omitted, the requesting user becomes the manager.

A successful response returns 201 Created:

{
  "_id": "969b26df-ab3...",
  "createdAt": "2026-03-11T11:38:45.156Z",
  "createdBy": "64abc...",
  "updatedAt": "2026-03-11T11:38:45.156Z",
  "updatedBy": "64abc...",
  "name": "My Example Project",
  "description": "A project for demonstrating the API",
  "bbox": [
    -180,
    -85,
    180,
    85
  ],
  "properties": {
  },
  "defaultDataBucketId": "816d4486-f5a..."
}

Step 2 – Request existing projects

To request a specific Project by its ID, use the GET /api/v1/project/{projectId} endpoint.

GET /api/v1/project/\{projectId}

The Return looks the same as the response in Step 1.

To list all projects accessible to the authenticated user, use the GET /api/v1/projects endpoint. The request allows for multiple different query parameters to filter and sort the results. The limit and page parameters control pagination, the sort and orderBy parameters control sorting. These parameters control the form of the output and are part of most list endpoints. The project list also includes specific parameters that filter by name, description, createdAt, createdBy, updatedAt, updatedBy and bbox.

GET /api/v1/projects

Workflow 3: Assign a User to a Project

Requires MANAGER permission on the project.

Users must be explicitly assigned to a project before they can interact with it. Each assignment pairs a user with a role.

Step 1 – Look up the user’s ID

GET /api/v1/users

The users endpoint returns all the users in the system when the user has admin rights. For users that are only project manager, there is the user-profiles endpoint. This endpoint only returns all the users in the system with limited information (id and name).

GET /api/v1/user-profiles

Both Endpoints provide a filter option for the username. The users endpoint additionally provides filters for email, createdAt, createdBy, updatedAt and updatedBy. The return for users looks like this:

{
  "items": [
    {
      "_id": "v6WEM...",
      "createdAt": "2016-02-10T15:46:35.816Z",
      "updatedAt": "2025-08-21T07:35:09.841Z",
      "updatedBy": "v6WEM...",
      "username": "username",
      "email": "user@email.de"
    },
    ...
    ]
}

The return for user-profiles looks like this:

{
  "items": [
    {
      "_id": "v6WEM...",
      "username": "username"
    },
    ...
    ]
}

Both provide an ID that is needed for assigning a user to a project in Step 3.

Step 2 – Retrieve available roles

To assign a user to a project, you need to specify a role for the user. The available roles can be retrieved with the following endpoint:

GET /api/v1/iam/roles

The roles can be filtered with the standard table filters and a permission filter. This filter describes the ability the role has (e.g. getProject). The role that should be assigned to the user in a project should be related to the project. The user in a project should have at least getProject permissions. Based on this only two possible roles are returned: ProjectMember and ProjectManager. The ProjectManager role includes all permissions related to the project, while the ProjectMember role includes mostly read permissions.

{
  "items": [
    {
      "_id": "84e1b02c-2b...",
      "createdAt": "2023-09-06T08:04:23.379Z",
      "updatedAt": "2026-01-15T08:08:43.385Z",
      "createdBy": "internal",
      "updatedBy": "internal",
      "name": "projectManager",
      "permissions": [
        "getProject",
        "getDatasource",
        "publishDatasource",
        "getUser",
        "getACL",
        "getCredentials",
        "createApp",
        "createModule",
        "editProject",
        "createDatasource",
        "deleteDatasource",
        "editDatasource",
        "createDatabase",
        "getDatabase",
        "deleteDatabase",
        "accessDbImport",
        "accessDbExport",
        "accessDbDelete",
        "accessObjectJob",
        "accessApplyUpdateJob",
        "accessObliqueJob",
        "accessPanoramaJob",
        "accessPointcloudJob",
        "accessSolarJob",
        "accessTerrainJob",
        "accessTMSJob",
        "accessCityDbInfo",
        "grantRole",
        "revokeRole",
        "getJob",
        "abortJob",
        "createDataBucket",
        "getDataBucket",
        "editDataBucket",
        "uploadToDataBucket",
        "deleteDataBucket",
        "getApp",
        "editApp",
        "deleteApp",
        "publishApp",
        "setAppPublic",
        "getModule",
        "editModule",
        "deleteModule"
      ]
    },
    {
      "_id": "be48df30-40...",
      "createdAt": "2023-09-06T08:04:23.381Z",
      "updatedAt": "2023-09-06T08:04:23.381Z",
      "createdBy": "internal",
      "updatedBy": "internal",
      "name": "projectMember",
      "permissions": [
        "getProject",
        "getDatasource",
        "publishDatasource",
        "getUser",
        "getACL",
        "getCredentials",
        "createScenario",
        "createApp",
        "createModule"
      ]
    }
  ],
  "limit": 20,
  "page": 0,
  "totalCount": 2,
  "totalPages": 1
}

Step 3 – Assign the user

If the requried prerequisits are met (userId, projectId and roleId), the user can be assigned to the project with the following endpoint.

PUT /api/v1/project/\{projectId}/user/{userId}
Content-Type: application/json

{
  "roleId": "<role-id-from-step-2>"
}

A successful response is 204 No Content.

Step 4 – Verify the assignment

GET /api/v1/project/\{projectId}/users

This endpoint returns a list of all users assigned to the project, including their roles. === Remove a user from a project

DELETE /api/v1/project/\{projectId}/user/{userId}

This Endpoint allows to remove a user from a project. Append ?purge=true to also remove any Guest App permissions in the same operation.

Workflow 4: Run a Processing Job

A task defines what should be processed and when. A job is the actual execution instance created when the task runs.

Step 1 – Create a task

POST /api/v1/project/\{projectId}/task

{
  "labels": [],
  "tags": {},
  "debugLevel": 2,
  "priority": 1,
  "name": "Test_api_task",
  "description": "task with api settings",
  "parameters": {
  "datasource": {
    "command": "create",
    "name": "Vector_Test"
  },
  "options": {
    "input": {
      "dataSource": {
        "@type": "GeoJSONSource",
        "fileSet": [
          {
            "dir": "C:\\path\\to\\input\\data",
            "fileName": [
              {
                "name": "*.json"
              }
            ]
          }
        ],
        "extrudeInfo": {
          "extrudePoints": false,
          "extrudeLines": false,
          "extrudePolygons": false,
          "heightPropertyName": "",
          "defaultExtrudedHeight": 0
        },
        "defaultColor": {
          "@type": "ColorSymbol",
          "value": "#cccccc"
        }
      },
      "heightMode": {
        "type": "absolute",
        "offset": 0
      }
    },
    "modelOptions": {
      "defaultShininess": 0.2,
      "defaultCreaseAngle": 10,
      "renderPrimitiveOutlines": {
        "value": false,
        "color": "#000000",
        "outlineMode": "Surfaces",
        "polygonArray": true
      }
    },
    "tiling": {
      "geometricErrorComputationStrategy": {
        "@type": "GeometricErrorComputationRelativeToTileSizeStrategy",
        "geometricErrorToTileSizeRatio": 0.0115866254
      },
      "value": {
        "@type": "SingleLevelTiling",
        "spatialSubdivisionSchema": {
          "@type": "QuadtreeSubdivision"
        },
        "fixedGeneralization": {
          "@type": "FixedGeneralization",
          "tolerance": 0,
          "texelSize": 0
        },
        "fixedObjectSizeFilter": {
          "@type": "FixedObjectSizeFilter",
          "thresholdValue": 0
        },
        "tileLevel": 15
      },
      "featureSelectionMode": "containsCenter"
    },
    "processing": {
      "convertMaterialsToTextures": false
    },
    "output": {
      "tiles3D_1_1": {
        "layerJson": {
          "maxLevelsInTileset": 3,
          "prettyPrint": true
        },
        "gltfOptions": {
          "exportMetadata": true,
          "addSubfeaturesIndicator": true,
          "backFaceCulling": true,
          "transparencyMode": "combined",
          "alphaFilterThreshold": 0.8,
          "metallicFactor": 0,
          "minFilter": "linear",
          "magFilter": "linear",
          "prettyPrint": true,
          "reverseFeatureIdLabels": true,
          "srgbValuesInPbrBaseColorFactor": false,
          "usePrimitiveOutlineExtension": true,
          "useMaterialsUnlitExtension": false,
          "useUnsignedShortIndexAccessors": false,
          "imageEncoding": "jpeg_png",
          "jpegOptions": {
            "jpegCompressionQuality": 0.8,
            "jpegChromaSubsampling": "4_2_2"
          },
          "pngOptions": {
            "pngCompressionQuality": 0.5
          }
        }
      }
    }
  }
},
  "properties": {},
  "jobType": "vcTilesVector",
  "jobVersion": "",
  "schedule": {
    "type": "immediate"
  }
}

The jobType field determines the type of processing. The parameters object is job-type specific.

The schedule field controls when the job runs:

Schedule value Behaviour

{ "type": "immediate" }

The job runs as soon as a processing runner is available

{ "type": "scheduled", "scheduled": "2026-03-12T08:00:00Z" }

The job runs at a specific UTC date and time

{ "type": "cron", "cron": "0 3 * * *" }

The job repeats on a CRON schedule

A successful response returns 200 OK with the created task object including its _id.

{
  "jobVersion": "",
  "priority": 1,
  "debugLevel": 2,
  "tags": {},
  "labels": [],
  "properties": {},
  "name": "Test_api_task",
  "description": "task with api settings",
  "jobType": "vcTilesVector",
  "schedule": {
    "type": "immediate"
  },
  "projectId": "b68056fc-86ed-4681-...",
  "createdAt": "2026-03-12T13:59:38.664Z",
  "updatedAt": "2026-03-12T13:59:38.664Z",
  "createdBy": "v6WEM2A92...",
  "updatedBy": "v6WEM2A92...",
  "_id": "07047f43-26ed-4a40-...",
  "lastJobId": "954b1c80-7917-4ca0-...",
  "parameters": { ... },
}

Workflow 5: Upload Data & Create a Datasource

Part A – Upload Data to a Data Bucket

A data bucket is a storage container within a project. Every project has a default data bucket, created automatically alongside the project.

Step 1 – Find the target data bucket

GET /api/v1/project/\{projectId}/data-buckets

The response includes a list of data buckets in the project.

{
  "items": [
    {
      "_id": "57d6913d-af53-4292-...",
      "properties": {},
      "name": "default-b68056fc-86ed-...",
      "createdAt": "2026-01-08T09:15:15.741Z",
      "updatedAt": "2026-01-08T09:15:15.741Z",
      "createdBy": "v6WEM2A92ozfZaAu5",
      "updatedBy": "v6WEM2A92ozfZaAu5",
      "projectId": "b68056fc-86ed-4681-..."
    }
  ],
  "limit": 20,
  "page": 0,
  "totalCount": 1,
  "totalPages": 1
}

Step 2 – Upload a file

POST /api/v1/project/\{projectId}/data-bucket/{dataBucketId}/upload

Append overwrite=true to overwrite an existing file with the same name. The Swagger UI provides a file upload field that allows users to navigate to the file on their local machine.

A successful single-file upload returns 204. When uploading multiple files, the API may return 207 Multi-Status if only some files succeeded.

Part B – Create a Datasource

A datasource is a registered reference to data stored in a data bucket or at an external URL.

There are two source types: internal (data bucket) and external (URL).

Internal datasource:

POST /api/v1/project/\{projectId}/datasource

{
  "name": "Datasource Name",
  "type": "geojson",
  "typeProperties": {
    "screenSpaceError": 16
  },
  "sourceProperties": {
    "type": "internal",
    "dataBucketId": "{dataBucketId}",
    "dataBucketKey": "path/to/uploaded/file.json"
  }
}

External datasource:

POST /api/v1/project/\{projectId}/datasource

{
  "name": "Datasource Name",
  "type": "wms",
  "typeProperties": {
    "layers": ["layer_name"],
    "version": "1.3.0",
    "format": "image/png"
  },
  "sourceProperties": {
    "type": "external",
    "url": "https://example.com/wms"
  }
}

The following type values are supported:

Type Description

tileset

3D Tileset (e.g. Cesium 3D Tiles)

geojson

GeoJSON vector data

oblique

Oblique imagery

qmesh

Quantized Mesh terrain

meshinmesh

Terrain mesh in mesh

wms

Web Map Service

wmts

Web Map Tile Service

tms

Tile Map Service

cog

Cloud Optimized GeoTIFF

flatgeobuf

FlatGeobuf vector data

i3s

I3S Scene Layer

vectortiles

Vector Tiles

generic

Generic / custom type

panorama

Panoramic imagery

A successful response returns 201 Created:

{
  "_id": "ds-id-xyz...",
  "name": "Datasource Name",
  "type": "geojson",
  "uri": "https://publisher-url.de/data/...",
  "projectId": "proj-id-abc123",
  "jobIds": [],
  "publishTaskIds": [],
  "dataUpdatedAt": "2026-03-11T09:30:00.000Z",
  "dataUpdatedBy": "user-id"
}

Step 5 – List all datasources

GET /api/v1/project/\{projectId}/datasources

Workflow 6: Create an App with Modules

Step 1 – Create an app

To create an app, send a POST request to /api/v1/project/{projectId}/app with the app name, description, and an array of module IDs. The mapVersion field specifies the VC Map viewer engine version as a semver range: Additionally a MemberId can be added as a query string. If no MemberId is provided, the app will be created with the permissions of the requesting user. If a MemberId is provided, the app will be created with the permissions of that user. The MemberId needs to be assigned to the project already.

POST /api/v1/project/\{projectId}/app
{
  "name": "My City App",
  "description": "Public-facing 3D city app",
  "mapVersion": "^6.0.0",
  "moduleIds": ["module-id-abc"]
}

If multiple modules are listed in moduleIds, their layers and viewpoints are merged sequentially. The creation of a module is described in the next step, but modules can also be created independently and added to the app later.

A successful response returns 201 Created:

{
  "_id": "app-id-xyz",
  "name": "My City App",
  "projectId": "proj-id-abc123",
  "moduleIds": ["module-id-abc"],
  "mapVersion": "^6.0.0",
  "publishTaskIds": [],
  "isPublic": false,
  "createdAt": "2026-03-13T10:47:45.931Z",
  "updatedAt": "2026-03-13T10:47:45.931Z",
  "createdBy": "v6WEM2A92ozfZaAu5",
  "updatedBy": "v6WEM2A92ozfZaAu5"
}

Save the _id as {appId}. The app is not publicly accessible until it is published in Step 6.

Step 2 – Create a module

POST /api/v1/project/\{projectId}/module

{
   "name":"My Base Module",
   "description":"description of my base module",
   "properties":{
   },
   "layers":[
      {
         "type":"OpenStreetMapLayer",
         "name":"OpenStreetMapLayer(1)",
         "properties":{
            "attributions":{
               "provider":"OpenStreetMap contributors",
               "url":"http://www.openstreetmap.org/",
               "year":"2026"
            }
         },
         "activeOnStartup":true,
         "zIndex":0
      },
      {
         "type":"GeoJSONLayer",
         "name":"test_api_datasource",
         "activeOnStartup":true,
         "url":"./datasource-data/f4a59d97-4401-45ab-b31a-2e5ad6569fc9/testLake3_withZ.json",
         "extent":{
            "coordinates":[
               12.278030916360558,
               53.76142514632812,
               12.32886225819537,
               53.778920631506764
            ],
            "projection":{
               "type":"Projection",
               "epsg":"EPSG:4326"
            },
            "type":"Extent"
         },
         "datasourceId":"f4a59d97-4401-45ab-b31a-2e5ad6569fc9",
         "zIndex":0
      }
   ],
   "maps":[
      {
         "type":"CesiumMap",
         "name":"CesiumMap"
      }
   ],
   "contentTree":[
      {
         "type":"LayerContentTreeItem",
         "name":"OpenStreetMapLayer(1)",
         "layerName":"OpenStreetMapLayer(1)"
      }
   ],
   "startingMapName":"CesiumMap",
}

The layers in the module can reference datasources created in Workflow 5. A successful response returns 201 Created:

{
  "_id": "module-id-abc",
  "name": "My Base Module",
  "projectId": "proj-id-abc123",
  "layers": [ ... ],
  "viewpoints": [ ... ],
  "createdAt": "2026-03-11T09:45:00.000Z"
}

Step 3 – Add modules

This replaces the existing moduleIds array entirely — include all modules, not just the new ones: Only the fields that should be overwritten need to be included in the request body.

PUT /api/v1/project/\{projectId}/app/\{appId}

{
  "moduleIds": ["module-id-abc", "module-id-second"]
}