Importing landscapes

This guide shows how to import and maintain landscapes as code using the LandscapeImportData JSON schema.

This is useful for:

  • Generating diagrams with LLMs
  • Syncing landscapes via CI/CD pipelines
  • Maintaining diagrams in a git repository

Prerequisites:

  • IcePanel account
  • API key (created from https://app.icepanel.io/organizations/:organizationId/manage/api-keys)

Steps

1

Select a landscape

To import diagrams into a landscape, you will need the landscape ID, which can be found in the URL:

https://app.icepanel.io/landscapes/:landscapeId/versions/latest/overview

Or, you can get the landscape ID with a GET request to the /landscapes endpoint.

GET
/v1/organizations/:organizationId/landscapes
curl https://api.icepanel.io/v1/organizations/organizationId/landscapes \
-H "X-API-Key: <apiKey>"

This returns a list of all landscapes in your organization. Note the id of the landscape you want to import into.

2

Create your import file

Your landscape can be modelled in a JSON or YAML file by using the LandscapeImportData JSON schema. Each data model has a persistent id field, which is useful for upserting existing models.

{
"modelObjects": [
{
"id": "domain-ecommerce",
"name": "E-Commerce Platform",
"parentId": null,
"type": "domain"
},
{
"id": "person-customer",
"name": "Customer",
"parentId": "domain-ecommerce",
"type": "actor",
"tagIds": ["tag-external"]
},
{
"id": "person-admin",
"name": "Admin User",
"parentId": "domain-ecommerce",
"type": "actor",
"tagIds": ["tag-internal"]
},
{
"id": "system-storefront",
"name": "Storefront",
"parentId": "domain-ecommerce",
"type": "system",
"tagIds": ["tag-internal"]
},
{
"id": "system-payment",
"name": "Payment Gateway",
"parentId": "domain-ecommerce",
"type": "system",
"tagIds": ["tag-external"]
},
{
"id": "system-email",
"name": "Email Service",
"parentId": "domain-ecommerce",
"type": "system",
"tagIds": ["tag-external"]
},
{
"id": "container-web",
"name": "Web App",
"parentId": "system-storefront",
"type": "app",
"tagIds": ["tag-internal"]
},
{
"id": "container-api",
"name": "API Server",
"parentId": "system-storefront",
"type": "app",
"tagIds": ["tag-internal"]
},
{
"id": "container-db",
"name": "Product Database",
"parentId": "system-storefront",
"type": "database",
"tagIds": ["tag-internal"]
},
{
"id": "container-cache",
"name": "Cache",
"parentId": "system-storefront",
"type": "database",
"tagIds": ["tag-internal"]
},
{
"id": "component-product-service",
"name": "Product Service",
"parentId": "container-api",
"type": "component",
"tagIds": ["tag-internal"]
},
{
"id": "component-order-service",
"name": "Order Service",
"parentId": "container-api",
"type": "component",
"tagIds": ["tag-internal"]
},
{
"id": "component-auth-service",
"name": "Auth Service",
"parentId": "container-api",
"type": "component",
"tagIds": ["tag-internal"]
},
{
"id": "component-notification-service",
"name": "Notification Service",
"parentId": "container-api",
"type": "component",
"tagIds": ["tag-internal"]
}
],
"modelConnections": [
{
"id": "conn-customer-web",
"name": "Browses store via HTTPS",
"direction": "outgoing",
"originId": "person-customer",
"targetId": "container-web"
},
{
"id": "conn-admin-web",
"name": "Manages catalog via HTTPS",
"direction": "outgoing",
"originId": "person-admin",
"targetId": "container-web"
},
{
"id": "conn-web-api",
"name": "REST API calls",
"direction": "outgoing",
"originId": "container-web",
"targetId": "container-api"
},
{
"id": "conn-api-db",
"name": "Reads/writes",
"direction": "outgoing",
"originId": "container-api",
"targetId": "container-db"
},
{
"id": "conn-api-cache",
"name": "Caches product data",
"direction": "outgoing",
"originId": "container-api",
"targetId": "container-cache"
},
{
"id": "conn-order-payment",
"name": "Processes payment",
"direction": "outgoing",
"originId": "component-order-service",
"targetId": "system-payment"
},
{
"id": "conn-notification-email",
"name": "Sends order confirmations",
"direction": "outgoing",
"originId": "component-notification-service",
"targetId": "system-email"
},
{
"id": "conn-product-db",
"name": "Queries product catalog",
"direction": "outgoing",
"originId": "component-product-service",
"targetId": "container-db"
},
{
"id": "conn-auth-db",
"name": "Reads user records",
"direction": "outgoing",
"originId": "component-auth-service",
"targetId": "container-db"
},
{
"id": "conn-order-notification",
"name": "Triggers notifications",
"direction": "outgoing",
"originId": "component-order-service",
"targetId": "component-notification-service"
}
],
"tagGroups": [
{
"id": "tag-group-ownership",
"name": "Ownership",
"icon": "tag"
}
],
"tags": [
{
"id": "tag-internal",
"name": "Internal",
"color": "blue",
"groupId": "tag-group-ownership"
},
{
"id": "tag-external",
"name": "External",
"color": "orange",
"groupId": "tag-group-ownership"
}
]
}
# yaml-language-server: $schema=https://api.icepanel.io/v1/schemas/LandscapeImportData
tagGroups:
- id: tag-group-ownership
name: Ownership
icon: tag
tags:
- id: tag-internal
name: Internal
color: blue
groupId: tag-group-ownership
- id: tag-external
name: External
color: orange
groupId: tag-group-ownership
modelObjects:
- id: domain-ecommerce
name: E-Commerce Platform
type: domain
- id: person-customer
name: Customer
type: actor
parentId: domain-ecommerce
tagIds:
- tag-external
- id: person-admin
name: Admin User
type: actor
parentId: domain-ecommerce
tagIds:
- tag-internal
- id: system-storefront
name: Storefront
type: system
parentId: domain-ecommerce
tagIds:
- tag-internal
- id: system-payment
name: Payment Gateway
type: system
parentId: domain-ecommerce
tagIds:
- tag-external
- id: system-email
name: Email Service
type: system
parentId: domain-ecommerce
tagIds:
- tag-external
- id: container-web
name: Web App
type: app
parentId: system-storefront
tagIds:
- tag-internal
- id: container-api
name: API Server
type: app
parentId: system-storefront
tagIds:
- tag-internal
- id: container-db
name: Product Database
type: database
parentId: system-storefront
tagIds:
- tag-internal
- id: container-cache
name: Cache
type: database
parentId: system-storefront
tagIds:
- tag-internal
- id: component-product-service
name: Product Service
type: component
parentId: container-api
tagIds:
- tag-internal
- id: component-order-service
name: Order Service
type: component
parentId: container-api
tagIds:
- tag-internal
- id: component-auth-service
name: Auth Service
type: component
parentId: container-api
tagIds:
- tag-internal
- id: component-notification-service
name: Notification Service
type: component
parentId: container-api
tagIds:
- tag-internal
modelConnections:
- id: conn-customer-web
name: Browses store via HTTPS
direction: outgoing
originId: person-customer
targetId: container-web
- id: conn-admin-web
name: Manages catalog via HTTPS
direction: outgoing
originId: person-admin
targetId: container-web
- id: conn-web-api
name: REST API calls
direction: outgoing
originId: container-web
targetId: container-api
- id: conn-api-db
name: Reads/writes
direction: outgoing
originId: container-api
targetId: container-db
- id: conn-api-cache
name: Caches product data
direction: outgoing
originId: container-api
targetId: container-cache
- id: conn-order-payment
name: Processes payment
direction: outgoing
originId: component-order-service
targetId: system-payment
- id: conn-notification-email
name: Sends order confirmations
direction: outgoing
originId: component-notification-service
targetId: system-email
- id: conn-product-db
name: Queries product catalog
direction: outgoing
originId: component-product-service
targetId: container-db
- id: conn-auth-db
name: Reads user records
direction: outgoing
originId: component-auth-service
targetId: container-db
- id: conn-order-notification
name: Triggers notifications
direction: outgoing
originId: component-order-service
targetId: component-notification-service
3

Import your landscape

You can import the file through the UI or the API.

Each id in your import file maps to a resource in IcePanel. It’s either created if the resource does not exist or updated otherwise.

From the landscape page, click Import model > File Upload and upload your file.

Import landscape file

4

Set up CI/CD

To keep your landscape in sync automatically, call the import endpoint in a CI/CD job on every push to your main branch.

Add two repository secrets in GitHub (Settings > Secrets and variables > Actions):

  • ICEPANEL_LANDSCAPE_ID
  • ICEPANEL_API_KEY

Create a workflow file at .github/workflows/icepanel-sync.yml:

your-project
.github
workflows
icepanel-sync.yml
icepanel-landscape-import.yaml
README.md
.github/workflows/icepanel-sync.yml
name: Synchronise IcePanel landscape
on:
push:
branches:
- main
- master
jobs:
synchronise-landscape:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Import landscape
run: |
curl -sf -X POST "https://api.icepanel.io/v1/landscapes/${{ vars.ICEPANEL_LANDSCAPE_ID }}/versions/latest/import" \
-H "X-API-Key: ${{ secrets.ICEPANEL_API_KEY }}" \
-H "Content-Type: application/yaml" \
--data-binary @icepanel-landscape-import.yaml

This triggers the synchronise-landscape job on every push to main/master.

Generating diagrams with LLMs

LLMs can generate a valid import file from a description of your architecture. The LandscapeImportData schema is publicly accessible, so you can point the LLM directly at it to validate the output.

See an example prompt that generates an import file for IcePanel.

Prompt
Generate an IcePanel landscape import data model based on the C4 model from the infrastructure and software architecture of the platform.
Parse infrastructure files to understand the platform and use it to map the infrastructure into C4 model abstraction.
Do not create objects or connections that don't exist or are strongly implied by the infrastructure.
Fetch LandscapeImportData JSONSchema from api.icepanel.io/v1/schemas/LandscapeImportData along with nested schemas and strictly follow the schema to create a YAML file for import into IcePanel.
Fetch docs.icepanel.io/core-features/modelling.md for context on how to create IcePanel C4 model structures.
Infrastructure can be categorized into technologies by fetching the JSONSchema from api.icepanel.io/v1/schemas/CatalogTechnologyType.
Perform an exhaustive search for technologies using http requests to api.icepanel.io/v1/catalog/technologies?filter[name]=NodeJS and add the id to the technologyIds array.
If a technology cannot be found try reducing the search term to be more specific to find a match.
If a primary technology exists then assign the visual icon to icon.technologyId.
Write the resulting import data to a YAML file to the file named icepanel-landscape-import.yaml in the current directory.
Prefix the YAML file with # yaml-language-server: $schema=https://api.icepanel.io/v1/schemas/LandscapeImportData.

Example output:

Prune option

By default, data models that exist in IcePanel but are missing from your import file are left untouched. To have IcePanel delete models not present in the import file, add the prune=true query parameter.

Note that prune=true is a destructive operation. Any model objects, connections, tags, or tag groups in IcePanel that are not in your import file will be permanently deleted. Make sure your import file is the complete source of truth before using this option.

curl -sf -X POST "https://api.icepanel.io/v1/landscapes/$ICEPANEL_LANDSCAPE_ID/versions/latest/import?prune=true" \
-H "X-API-Key: $ICEPANEL_API_KEY" \
-H "Content-Type: application/yaml" \
--data-binary @icepanel-landscape-import.yaml

Namespace option

A namespace is a label that groups models by their import source. You can add a namespace to your import file to separate models by import source. When used with prune=true, only models in the same namespace are deleted. Models from other namespaces are left untouched.

This is useful for large organizations running multiple CI/CD pipelines from different repositories against the same landscape, with each pipeline managing its own models independently.

You can set the namespace in your YAML file:

# yaml-language-server: $schema=https://api.icepanel.io/v1/schemas/LandscapeImportData
namespace: my-repo
modelObjects:
- id: system-storefront
name: Storefront
type: system

Or pass it directly in the request body when using JSON:

curl -sf -X POST "https://api.icepanel.io/v1/landscapes/$ICEPANEL_LANDSCAPE_ID/versions/latest/import" \
-H "X-API-Key: $ICEPANEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"namespace": "my-repo",
"modelObjects": [...]
}'