Documentation

Smart Tags

Smart tags are virtual tags ZopNight derives from tagging policies you define. Each policy enforces a tag key and derives its value from the resource's own fields, so the result is reproducible and cheap to re-run. Smart tags are applied inside ZopNight for cost attribution only; they are never written back to the cloud provider.

Why this matters

Tag-based cost attribution and reporting rely on consistent tags. In practice most accounts have partial coverage — smart tags close the gap so Showback and Reports can attribute cost even on resources the cloud account owner never tagged.

How derivation works

  1. You create a tagging policy: an optional selector (which resources it applies to — by provider, type, region, …) plus a tag spec (the key, and the value built from resource fields).
  2. The recommender resolves each resource against its policies. For a match, it derives the tag value: fields → join → standardize (e.g. res.provider + res.region joined by -, lowercased → gcp-us-central1).
  3. Derived tags land as pending. They are never auto-applied to cost — you accept the ones you want (or revoke later).
  4. pending = derived − accepted. There is no "rejected" state.

Value source fields are restricted to stable resource attributes — res.type, res.region, res.provider, res.instance_type, res.name. Empty parts are skipped so a partially-absent multi-field value never produces separator garbage; an all-empty value omits the tag (there is no literal fallback).

Lifecycle & reconciliation

Tags are re-derived per resource on each resource.discovered event and across the org on a full refresh (POST /recommendations/refresh). Every re-derivation reconciles your accepted tags: an accepted tag is kept only if it is still derived with the same value, otherwise it is dropped — so disabling a policy or changing its value automatically removes the now-unsourced tag on the next refresh. When a resource is deleted in the cloud, its smart-tag row is removed.

List Smart Tags

GET
/smart-tags

Paginated per-resource derived/accepted tags for the current org, plus org-wide pending/accepted counts for the tab badges.

Query Parameters

ParameterDescription
statusLifecycle filter: pending | accepted. Omit for all.
providerCloud provider filter: aws | gcp | azure.
pagePage number (default: 1).
sizeItems per page (default: 20, max: 100).
Responsejson
{
  "items": [
    {
      "resourceUid": "//artifactregistry.googleapis.com/projects/acme/locations/us-central1/repositories/web",
      "resourceName": "web",
      "resourceType": "artifact-registry",
      "provider": "gcp",
      "cloudAccountId": "acc_1",
      "derived":  { "cost-allocation": "gcp-us-central1", "cloud-platform": "gcp" },
      "accepted": { "cloud-platform": "gcp" },
      "pending":  { "cost-allocation": "gcp-us-central1" }
    }
  ],
  "hasMore": false,
  "limit": 20,
  "counts": { "pending": 132, "accepted": 6 }
}

Accept or Revoke

PATCH
/smart-tags

Promote pending tags into cost attribution (accept) or remove them (revoke). Body-based so resourceUid may contain slashes (e.g. GCP //... paths).

Send accepted: true to accept or accepted: false to revoke. Name the keys with keys: [...], or pass all: true (accept only) to act on every derived key. A mutation with no keys and all unset is rejected — it would otherwise silently no-op.

Accept specific keysbash
curl -X PATCH https://zopnight.com/api/smart-tags \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "resourceUid": "//artifactregistry.googleapis.com/projects/acme/locations/us-central1/repositories/web",
    "keys": ["cost-allocation"],
    "accepted": true
  }'
Accept every derived key on a resourcebash
curl -X PATCH https://zopnight.com/api/smart-tags \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{ "resourceUid": "i-0abc123def456", "all": true, "accepted": true }'
Revoke (back to pending)bash
curl -X PATCH https://zopnight.com/api/smart-tags \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "resourceUid": "i-0abc123def456",
    "keys": ["cost-allocation"],
    "accepted": false
  }'

Tagging Policies

Tagging policies are stored in the generic policy store and managed through the standard policy CRUD endpoints with domain: "tagging". The recommender serves the form vocabulary and validates a spec before you create it.

POST
/policies

Create a tagging policy (domain=tagging).

Requestbash
curl -X POST https://zopnight.com/api/policies \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "tagging",
    "targetType": "organisation",
    "name": "cost-allocation",
    "spec": {
      "selector": { "all": [{ "attr": "res.provider", "op": "eq", "values": ["gcp"] }] },
      "tag": {
        "key": "cost-allocation",
        "value": { "fields": ["res.provider", "res.region"], "join": "-", "standardize": true }
      }
    },
    "enabled": true
  }'
GET
/tagging/policies/catalog

Form vocabulary: selectable condition facets, operators, and the allowlisted value-source fields.

POST
/tagging/policies/validations

Validate a tagging spec before create. Returns { valid, errors[] }.

Selector & value

The selector is a boolean tree (all / any / not over {attr, op, values} leaves; ops eq, neq, in, contains, regex, exists) — string ops match case-insensitively. The value.joinseparator is limited to - or _ (safe across AWS, GCP and Azure labels).