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.
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 |
|---|---|
|
The job runs as soon as a processing runner is available |
|
The job runs at a specific UTC date and time |
|
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 |
|---|---|
|
3D Tileset (e.g. Cesium 3D Tiles) |
|
GeoJSON vector data |
|
Oblique imagery |
|
Quantized Mesh terrain |
|
Terrain mesh in mesh |
|
Web Map Service |
|
Web Map Tile Service |
|
Tile Map Service |
|
Cloud Optimized GeoTIFF |
|
FlatGeobuf vector data |
|
I3S Scene Layer |
|
Vector Tiles |
|
Generic / custom type |
|
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"
}
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"]
}