Skip to content

API Assets

Maks Zaikin edited this page Jul 1, 2026 · 1 revision

title: "API — Assets" category: "api" version: "1.0" last_updated: "2024-01-15" standards: ["NIST-SP-800-53", "ISA-IEC-62443"] related_pages: ["API-Overview", "API-Credentials", "API-Maintenance", "Database-Assets-DB", "Workflow-Rotation-Task", "Security-Authorization-Model"] ai_summary: "VaultFlower Assets API endpoints. Full hierarchy: Locations, Systems, Zones, Assets. CRUD operations for each level. Criticality level changes require justification and generate CRITICAL SIEM events. Asset status updates (online/offline) trigger rotation scheduling. Shortcut GET /assets/{id} available without full hierarchy path."

🗺️ API — Assets

Base Path

https://vfw-core.contoso.com/api/v1/{tenant-slug}/

Asset hierarchy follows ISA/IEC 62443 Zones and Conduits model:

/locations
/locations/{id}/systems
/locations/{id}/systems/{id}/zones
/locations/{id}/systems/{id}/zones/{id}/assets
/assets/{id}  ← shortcut (no hierarchy path needed)

Locations

POST /locations

Create a new location. Admin only.

POST /api/v1/acme-corp/locations
Authorization: Bearer {admin_token}
Content-Type: application/json
X-Request-ID: {uuid}
{
  "name": "North-Site",
  "code": "PLT-NORTH",
  "country": "Country",
  "region": "Западная Сибирь",
  "city": "City",
  "address": "st. Address, h-no",
  "latitude": 60.9344,
  "longitude": 76.5531,
  "timezone": "Asia/Moon"
}

Response 201:

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "North-Site",
  "code": "PLT-NORTH",
  "timezone": "Asia/Moon",
  "is_active": true,
  "created_at": "2024-01-15T08:00:00Z"
}

Errors: 409 CODE_EXISTS — location code already in use for this tenant.

SIEM: location.created (INFO)


GET /locations

List all locations for the tenant.

GET /api/v1/acme-corp/locations?is_active=true&page=1&page_size=20
Authorization: Bearer {access_token}
X-Request-ID: {uuid}

Query parameters:

Parameter Type Default Description
is_active boolean true Filter by active status
page integer 1 Page number
page_size integer 20 Items per page (max 100)

Response 200:

{
  "items": [
    {
      "id": "a1b2c3d4-...",
      "name": "North-Site",
      "code": "PLT-NORTH",
      "country": "Country",
      "city": "City",
      "timezone": "Asia/Moon",
      "is_active": true,
      "systems_count": 3,
      "assets_count": 24
    }
  ],
  "total": 2,
  "page": 1,
  "page_size": 20,
  "total_pages": 1
}

Access: All roles (scope-filtered for non-Admin)


GET /locations/{location-id}

Get location details.

GET /api/v1/acme-corp/locations/a1b2c3d4-e5f6-7890-abcd-ef1234567890
Authorization: Bearer {access_token}
X-Request-ID: {uuid}

Response 200:

{
  "id": "a1b2c3d4-...",
  "name": "North-Site",
  "code": "PLT-NORTH",
  "country": "Country",
  "region": "Западная Сибирь",
  "city": "City",
  "address": "st. Address, h-no",
  "latitude": 60.9344,
  "longitude": 76.5531,
  "timezone": "Asia/Moon",
  "is_active": true,
  "created_at": "2024-01-15T08:00:00Z",
  "created_by": "admin@contoso.com",
  "systems_count": 3,
  "assets_count": 24
}

PATCH /locations/{location-id}

Update location. Admin only. Only provided fields are updated.

PATCH /api/v1/acme-corp/locations/a1b2c3d4-e5f6-7890-abcd-ef1234567890
Authorization: Bearer {admin_token}
Content-Type: application/json
X-Request-ID: {uuid}
{
  "address": "st. Address, ho-no-2",
  "city": "City"
}

Response 200: Updated location object.

SIEM: location.updated (INFO)


DELETE /locations/{location-id}

Deactivate location. Admin only. Soft delete — sets is_active = false.

DELETE /api/v1/acme-corp/locations/a1b2c3d4-e5f6-7890-abcd-ef1234567890
Authorization: Bearer {admin_token}
X-Request-ID: {uuid}

Response 204: Location deactivated.

Response 409: HAS_ACTIVE_SYSTEMS — location has active systems. Deactivate systems first.

SIEM: location.deactivated (WARN)


Systems

POST /locations/{location-id}/systems

Create a system within a location. Admin only.

POST /api/v1/acme-corp/locations/a1b2c3d4-.../systems
Authorization: Bearer {admin_token}
Content-Type: application/json
X-Request-ID: {uuid}
{
  "name": "SCADA-Production",
  "code": "SCADA-OIL-01",
  "system_type": "SCADA",
  "description": "Oil production management system",
  "criticality_level": "CRITICAL",
  "criticality_type": "COMBINED",
  "criticality_justification": "Controls oil production. Failure causes economic loss and environmental risk.",
  "owner_name": "John, Doe sr.",
  "owner_title": "Главный инженер",
  "owner_email": "j.doe.sr@company.ru",
  "owner_phone": "+7-999-123-45-67",
  "deputy_name": "Петров Александр Иванович",
  "deputy_email": "petrov.a@company.ru",
  "working_hours_start": "08:00",
  "working_hours_end": "20:00",
  "working_days": "1,2,3,4,5"
}

Response 201:

{
  "id": "b2c3d4e5-f6a7-8901-bcde-f01234567890",
  "name": "SCADA-Production",
  "code": "SCADA-OIL-01",
  "system_type": "SCADA",
  "criticality_level": "CRITICAL",
  "is_active": true,
  "created_at": "2024-01-15T08:00:00Z"
}

SIEM: system.created (INFO)


GET /locations/{location-id}/systems

List systems in a location.

GET /api/v1/acme-corp/locations/a1b2c3d4-.../systems
Authorization: Bearer {access_token}
X-Request-ID: {uuid}

Response 200:

{
  "items": [
    {
      "id": "b2c3d4e5-...",
      "name": "SCADA-Production",
      "code": "SCADA-OIL-01",
      "system_type": "SCADA",
      "criticality_level": "CRITICAL",
      "criticality_type": "COMBINED",
      "owner_name": "John, Doe sr.",
      "owner_email": "j.doe.sr@company.ru",
      "is_active": true,
      "zones_count": 4,
      "assets_count": 12
    }
  ],
  "total": 3,
  "page": 1,
  "page_size": 20,
  "total_pages": 1
}

GET /locations/{location-id}/systems/{system-id}

Get system details.

Response 200: Full system object including all owner fields and working hours.


PATCH /locations/{location-id}/systems/{system-id}

Update system. Admin only.

Special rule for criticality_level change:

{
  "criticality_level": "HIGH",
  "criticality_change_reason": "Risk reassessment per annual audit — downgraded from CRITICAL to HIGH"
}

criticality_change_reason is required when changing criticality_level. Missing reason → 400 REASON_REQUIRED.

SIEM:

  • system.updated (INFO) — general update
  • system.criticality_changed (CRITICAL) — criticality level changed

DELETE /locations/{location-id}/systems/{system-id}

Deactivate system. Admin only.

Response 409: HAS_ACTIVE_ZONES or HAS_ACTIVE_CHECKOUTS

SIEM: system.deactivated (WARN)


Zones

POST /locations/{location-id}/systems/{system-id}/zones

Create a zone. Admin only.

POST /api/v1/acme-corp/locations/a1b2c3d4-.../systems/b2c3d4e5-.../zones
Authorization: Bearer {admin_token}
Content-Type: application/json
X-Request-ID: {uuid}
{
  "name": "OT-DMZ-North",
  "code": "OT-DMZ-NORTH",
  "zone_type": "DMZ",
  "network_type": "OT_DOMAIN",
  "domain_name": "ot.contoso.com",
  "ip_range": "10.10.1.0/24",
  "is_air_gapped": false,
  "description": "IT/OT demilitarized zone — North plant"
}

Response 201:

{
  "id": "c3d4e5f6-a7b8-9012-cdef-012345678901",
  "name": "OT-DMZ-North",
  "code": "OT-DMZ-NORTH",
  "zone_type": "DMZ",
  "network_type": "OT_DOMAIN",
  "is_air_gapped": false,
  "is_active": true,
  "created_at": "2024-01-15T08:00:00Z"
}

SIEM: zone.created (INFO)


GET /locations/{location-id}/systems/{system-id}/zones

List zones in a system.

Response 200:

{
  "items": [
    {
      "id": "c3d4e5f6-...",
      "name": "OT-DMZ-North",
      "code": "OT-DMZ-NORTH",
      "zone_type": "DMZ",
      "network_type": "OT_DOMAIN",
      "domain_name": "ot.contoso.com",
      "ip_range": "10.10.1.0/24",
      "is_air_gapped": false,
      "is_active": true,
      "assets_count": 5
    }
  ],
  "total": 4,
  "page": 1,
  "page_size": 20,
  "total_pages": 1
}

GET /locations/{location-id}/systems/{system-id}/zones/{zone-id}

Get zone details.

Response 200: Full zone object.


PATCH /locations/{location-id}/systems/{system-id}/zones/{zone-id}

Update zone. Admin only.

SIEM: zone.updated (INFO)


DELETE /locations/{location-id}/systems/{system-id}/zones/{zone-id}

Deactivate zone. Admin only.

Response 409: HAS_ACTIVE_ASSETS or HAS_ACTIVE_CHECKOUTS

SIEM: zone.deactivated (WARN)


Assets

POST /locations/{location-id}/systems/{system-id}/zones/{zone-id}/assets

Create an asset. Admin only.

POST /api/v1/acme-corp/locations/a1b2c3d4-.../systems/b2c3d4e5-.../zones/c3d4e5f6-.../assets
Authorization: Bearer {admin_token}
Content-Type: application/json
X-Request-ID: {uuid}
{
  "name": "System1",    
  "code": "SRV-OT-001",
  "asset_type": "SERVER",
  "hostname": "system1",

### GET /locations/{location-id}/systems/{system-id}/zones/{zone-id}/assets/{asset-id}

Get full asset details.

```http
GET /api/v1/acme-corp/locations/a1b2c3d4-.../systems/b2c3d4e5-.../zones/c3d4e5f6-.../assets/d4e5f6a7-...
Authorization: Bearer {access_token}
X-Request-ID: {uuid}

Response 200:

{
  "id": "d4e5f6a7-b8c9-0123-defa-123456789012",
  "name": "System1",
  "code": "SRV-OT-001",
  "asset_type": "SERVER",
  "hostname": "system1",
  "fqdn": "system1.ot.contoso.com",
  "ip_address": "10.10.1.101",
  "mac_address": "00:1A:2B:3C:4D:5E",
  "os_type": "WINDOWS",
  "os_name": "Windows Server 2019",
  "os_version": "10.0.17763",
  "is_domain_joined": true,
  "domain_name": "ot.contoso.com",
  "is_online": false,
  "last_seen_at": "2024-01-10T15:00:00Z",
  "rotation_method": "SCHEDULED_MANUAL",
  "rotation_port": null,
  "serial_number": "SN-12345-XYZ",
  "specification": {
    "cpu": "Intel Xeon E5-2680",
    "ram_gb": 32,
    "storage": "2x 500GB SSD RAID1"
  },
  "description": "Primary SCADA server for oil production control",
  "zone": {
    "id": "c3d4e5f6-...",
    "name": "OT-DMZ-North",
    "code": "OT-DMZ-NORTH",
    "zone_type": "DMZ",
    "is_air_gapped": false
  },
  "system": {
    "id": "b2c3d4e5-...",
    "name": "SCADA-Production",
    "code": "SCADA-OIL-01",
    "criticality_level": "CRITICAL"
  },
  "location": {
    "id": "a1b2c3d4-...",
    "name": "North-Site",
    "code": "PLT-NORTH"
  },
  "credentials_count": 2,
  "pending_tasks_count": 1,
  "is_active": true,
  "created_at": "2024-01-15T08:00:00Z",
  "created_by": "admin@contoso.com"
}

GET /assets/{asset-id}

Shortcut endpoint — get asset details without knowing the full hierarchy path.

GET /api/v1/acme-corp/assets/d4e5f6a7-b8c9-0123-defa-123456789012
Authorization: Bearer {access_token}
X-Request-ID: {uuid}

Response 200: Same as full hierarchy GET — includes zone, system, and location context.

Use case:

When you have an asset UUID (e.g. from a checkout record or task)
and don't need to navigate the full hierarchy to get details.
Blazor Portal uses this for quick asset lookup from task views.

PATCH /locations/{location-id}/systems/{system-id}/zones/{zone-id}/assets/{asset-id}

Update asset. Admin only. Only provided fields are updated.

PATCH /api/v1/acme-corp/locations/.../assets/d4e5f6a7-...
Authorization: Bearer {admin_token}
Content-Type: application/json
X-Request-ID: {uuid}
{
  "description": "Updated: Primary SCADA server — firmware updated to v3.2",
  "os_version": "10.0.17763.5122",
  "specification": {
    "cpu": "Intel Xeon E5-2680",
    "ram_gb": 64,
    "storage": "2x 500GB SSD RAID1",
    "firmware": "3.2.0"
  }
}

Response 200: Updated asset object.

Special rule for rotation_method change:

{
  "rotation_method": "WINRM",
  "rotation_method_change_reason": "Asset now network-connected — switching to automated rotation"
}

rotation_method_change_reason is recommended when changing rotation method.

SIEM: asset.updated (INFO) or asset.rotation_method_changed (WARN)


DELETE /locations/{location-id}/systems/{system-id}/zones/{zone-id}/assets/{asset-id}

Deactivate asset. Admin only. Soft delete.

DELETE /api/v1/acme-corp/locations/.../assets/d4e5f6a7-...
Authorization: Bearer {admin_token}
X-Request-ID: {uuid}

Response 204: Asset deactivated.

Response 409 — Active checkout exists:

{
  "error": {
    "code": "HAS_ACTIVE_CHECKOUTS",
    "message": "Asset has active checkout. Revoke it before deactivation.",
    "active_checkout_id": "c3d4e5f6-..."
  }
}

Response 409 — Active tasks exist:

{
  "error": {
    "code": "HAS_ACTIVE_TASKS",
    "message": "Asset has IN_PROGRESS tasks. Complete or cancel them first.",
    "active_task_count": 1
  }
}

SIEM: asset.deactivated (WARN)


PATCH /locations/{location-id}/systems/{system-id}/zones/{zone-id}/assets/{asset-id}/status

Update asset online/offline status. Admin or Rotation Agent service account.

PATCH /api/v1/acme-corp/locations/.../assets/d4e5f6a7-.../status
Authorization: Bearer {access_token}
Content-Type: application/json
X-Request-ID: {uuid}
{
  "is_online": true,
  "last_seen_at": "2024-01-15T08:00:00Z"
}

Response 200:

{
  "id": "d4e5f6a7-...",
  "code": "SRV-OT-001",
  "is_online": true,
  "last_seen_at": "2024-01-15T08:00:00Z",
  "rotation_triggered": false
}

Behavior on status change:

is_online: false → true:
  ✓ Asset marked online
  ✓ Worker checks for ROTATION_PENDING credentials
  ✓ If found: rotation tasks re-queued to RabbitMQ
  ✓ rotation_triggered: true in response
  SIEM: asset.online (INFO)

is_online: true → false:
  ✓ Asset marked offline
  ✓ Any AUTO rotation tasks paused (moved to SCHEDULED_MANUAL logic)
  SIEM: asset.offline (WARN)

Password Policies

GET /password-policies

List password policies for the tenant.

GET /api/v1/acme-corp/password-policies
Authorization: Bearer {access_token}
X-Request-ID: {uuid}

Response 200:

{
  "items": [
    {
      "id": "uuid",
      "name": "DEFAULT",
      "is_system": true,
      "min_length": 16,
      "max_length": 64,
      "require_uppercase": true,
      "require_lowercase": true,
      "require_digits": true,
      "require_special": true,
      "exclude_ambiguous": true,
      "history_count": 10,
      "max_age_days": null,
      "is_active": true
    },
    {
      "id": "uuid",
      "name": "LEGACY",
      "is_system": true,
      "min_length": 8,
      "max_length": 32,
      "require_uppercase": true,
      "require_lowercase": true,
      "require_digits": true,
      "require_special": false,
      "exclude_ambiguous": true,
      "history_count": 5,
      "max_age_days": 90,
      "is_active": true
    },
    {
      "id": "uuid",
      "name": "HIGH_SECURITY",
      "is_system": true,
      "min_length": 32,
      "max_length": 64,
      "require_uppercase": true,
      "require_lowercase": true,
      "require_digits": true,
      "require_special": true,
      "exclude_ambiguous": true,
      "history_count": 20,
      "max_age_days": 30,
      "is_active": true
    }
  ],
  "total": 3,
  "page": 1,
  "page_size": 20,
  "total_pages": 1
}

POST /password-policies

Create a custom password policy. Admin only.

POST /api/v1/acme-corp/password-policies
Authorization: Bearer {admin_token}
Content-Type: application/json
X-Request-ID: {uuid}
{
  "name": "SCADA-Legacy-PLC",
  "description": "For older PLCs that don't support special characters",
  "min_length": 10,
  "max_length": 20,
  "require_uppercase": true,
  "require_lowercase": true,
  "require_digits": true,
  "require_special": false,
  "allowed_special_chars": null,
  "excluded_chars": "0O1lI",
  "exclude_ambiguous": true,
  "history_count": 8,
  "max_age_days": 60
}

Response 201: Created policy object.

SIEM: policy.created (INFO)


PATCH /password-policies/{policy-id}

Update a custom policy. Admin only. System policies (is_system = true) cannot be modified.

Response 409 — System policy:

{
  "error": {
    "code": "SYSTEM_POLICY_IMMUTABLE",
    "message": "System policies cannot be modified. Create a custom policy instead."
  }
}

SIEM: policy.updated (WARN)


DELETE /password-policies/{policy-id}

Deactivate a custom policy. Admin only. System policies cannot be deactivated.

Response 409 — In use:

{
  "error": {
    "code": "POLICY_IN_USE",
    "message": "Policy is assigned to active maintenance schedules.",
    "schedules_count": 3
  }
}

SIEM: policy.deactivated (WARN)


Asset Hierarchy — Quick Reference

GET /locations                                           List all locations
GET /locations/{id}                                      Location details
GET /locations/{id}/systems                              Systems in location
GET /locations/{id}/systems/{id}                         System details
GET /locations/{id}/systems/{id}/zones                   Zones in system
GET /locations/{id}/systems/{id}/zones/{id}              Zone details
GET /locations/{id}/systems/{id}/zones/{id}/assets       Assets in zone
GET /locations/{id}/systems/{id}/zones/{id}/assets/{id}  Asset details
GET /assets/{id}                                         Asset shortcut (no path)
GET /password-policies                                   List policies

Related Pages

Clone this wiki locally