Skip to main content

Customer templates

A customer template is a provider-scoped, reusable bundle of location template references. It captures the set of location blueprints you would normally roll out for a new customer, so a caller can replay the same location templates at customer-onboarding time instead of re-selecting them by hand.

What a customer template is (and isn't)

  • It is a list of references. The locationTemplateIds list is held verbatim as a list of UUIDs that point at location templates. Each id is validated at save time. The request is rejected if any entry doesn't match an existing, non-deleted location template.
  • It is not an active resource. Saving a customer template doesn't create customers or locations. Materialization is the caller's responsibility: read the template, look up each referenced location template, and replay them under the new customer.
  • It is scoped to a provider. The providerId is set on create and immutable. Authorization gates on the parent provider. Anyone with update on the provider can manage its customer templates.

Fields

FieldDescriptionExample
idUnique ID assigned at create time3f5a7b9c-1d2e-4f3a-9876-1234567890ab
providerIdParent provider — set on create, immutable8b14d4f9-3a52-4b91-8e7c-d3e44b5c1f2a
nameHuman-readable name, unique within the provider (1–255 chars). Identifies the template itself; it isn't the created customer's nameStandard customer rollout
customerNameDefault for the created customer's name — seeds the apply body's name. Required (1–255 chars). Supports {{variable}} substitution{{provider.name}} tenant
customerDescriptionDefault for the created customer's description — seeds the apply body's description. Optional (≤1000 chars). Supports {{variable}} substitutionManaged by {{provider.name}}
locationTemplateIdsList of location template UUIDs the template references. Each id must resolve to a live, non-deleted location template at save time
[
  "7e1d2c3b-4a5f-4678-9012-3456789abcde"
]
ownerIdentityIdIdentity that owns the templatec0ffee00-1111-2222-3333-444455556666
createdBy, createdAt, updatedAt, deletedAt, deletedByStandard lifecycle fields

Applying a customer template

Materializing a customer template into real resources is a two-step interaction against a single endpoint pair, mirroring the location template apply flow but one level up: the customer is created first, then each referenced location template is applied under it in order.

The split pays off when the caller is a UI: the top-level _placeholders plus each embedded location's own _placeholders are enough to render a nested form (customer header, one section per location, indexed connector rows) without the UI needing to know anything about the underlying schema. CLIs benefit too. connect-cli apply <customerTemplateId> can render the full preview, prompt the operator for each placeholder at every level, and submit the filled body in one POST.

See the API reference for request/response examples and the full path table.

The preview (GET)

GET /customer-templates/{templateId}/apply returns a CreateCustomerFromTemplate payload shaped exactly like the POST body, pre-filled from the template:

  • The new customer's name/description are seeded from the template's customerName/customerDescription.
  • The locationTemplateIds list is expanded server-side into a locations array: one CreateLocationFromTemplate per referenced location template, in order. Each entry is itself a location template preview (name, description, connectorTemplates, the deprecated inline connectors, nested _placeholders) — so a customer template apply carries the whole three-level chain in one body.
  • A top-level _placeholders list describes the caller-supplied customer fields ($.name, required; $.description).
  • Each embedded location entry carries its own _placeholders rooted at that location's payload ($.name, $.description, $.address, $.longitude, $.latitude, $.virtual, $.cloudProvider, $.cloudRegion; see Cloud placement). The $.customerId placeholder is deliberately omitted. The server injects the new customer's id during apply, so the caller must not fill it in.
  • Each placeholder's value echoes the underlying template, so the UI can show the original blueprint as defaults the caller can override. The customer's name/description are seeded from the template's customerName/customerDescription, and each location's name/description from its location template's locationName/locationDescription. Those values are rendered (with {{variable}} substitution) at apply time. The connector fields are provider-authored, so they are echoed but not surfaced as placeholders. For more info, see Variable substitution.

The preview strips fields that don't belong in the caller's submission: id, ownerIdentityId, and the createdBy/createdAt/updatedAt/deletedAt/deletedBy lifecycle fields. The providerId is always taken from the customer template, never from the caller. The whole list is READ_ONLY for Jackson, so a caller can echo the entire response straight back into POST without filtering.

The apply (POST)

The POST body is a CreateCustomerFromTemplate — a customer name/description plus a locations: List<CreateLocationFromTemplate>:

  • name and description are seeded from the template, then rendered. The preview pre-fills them from the template's customerName/customerDescription. The caller may keep or override them; any {{variable}} tokens they contain are rendered at apply time (against provider.* only; see Variable substitution) to become the new customer's name/description. The rendered name is validated for length and per-provider uniqueness.
  • locations order matters. The body's locations list MUST have the same length as the customer template's locationTemplateIds, in the same order. A length mismatch returns 400 Bad Request before any resource is created. Each entry is paired by index with its referenced location template, which is what the server uses to apply it.
  • providerId is server-set. Anything the caller sends for it is overwritten with the customer template's provider id before validation.
  • locations[*].customerId is server-set. The server creates the new customer first, then injects its id as customerId on every embedded location body before that location is materialized. Anything the caller sends is ignored.
  • Each embedded location's virtual/cloudProvider/cloudRegion is chosen per apply, same as a standalone location template apply. These fields aren't stored on the referenced location template; the caller sets them independently for every entry in the locations list.
  • locations[*].connectorTemplates[*] must line up with each referenced location template's connectorTemplateIds — one entry per reference, in order, exactly as on a standalone location template apply. A mismatched count is rejected before anything is created.
  • locations[*].connectorTemplates[*].locationId and locations[*].connectors[*].locationId are server-set. Same rule as the location template apply: overwritten per connector with the new location's id.
  • Errors name the entry they came from. A failure inside a nested connector reports its full path — locations[0].connectorTemplates[1].name — rather than collapsing to the request root, so a caller submitting a whole chain learns exactly which entry it got wrong.
  • The whole call is one transaction. The customer is inserted, then each location and its connectors are bound, validated, and created in order. Any failure (bean validation, authorization, a Ziti-side error) rolls the whole thing back, including the customer. Ziti compensations registered against the connector creates fire on rollback so the network state stays consistent with the database.

Authorization

The caller needs three things in combination:

  1. Read on the customer template: Gated through its parent provider.
  2. Create on customers under the template's provider. This is verified up front, before any resource is touched.
  3. Create on locations and connectors at each new location's path. This is verified per location and per connector during materialization. A caller who has customer-create but not location- or connector-create will get past the Customer step and then see the whole apply roll back when the first per-location check fails.

Variable substitution

The apply body's name and description (seeded from the template's customerName/customerDescription) support {{variable}} placeholders, resolved at apply time. Because the customer is the resource being created, the only namespace in scope is provider.*. There is no customer.* (it would be circular) or location.* (no location exists yet):

PropertyIn-scope variable namespaces
$.nameprovider.id, provider.name, provider.networkId, provider.networkGroupId, provider.organizationId, provider.custom.*
$.descriptionprovider.id, provider.name, provider.networkId, provider.networkGroupId, provider.organizationId, provider.custom.*

provider.custom.* covers the provider's user-defined custom variables, e.g. {{provider.custom.edgeDomain}}.

Substitution is strict: a placeholder that references a variable not in the catalog (a typo, a custom variable not defined on the provider, or any customer.*/location.* token) fails the whole apply with a 4xx listing the offending variables, rather than emitting a blank or a literal {{…}}. The embedded location templates render their own fields (including customer.* and location.*) later, once the customer and each location exist. See the location template Variable substitution.

The provider's user-defined {{provider.custom.<name>}} variables are in scope here too, alongside the standard provider.* keys; customer.custom.* is not (the customer does not exist yet). See Custom variables.

Lifecycle hooks

Customer Connect listens for resource-change events from upstream resources and keeps templates in sync:

  • Provider deleted: Every customer template under that provider is deleted in turn. The cascade is driven by the connect-provider DELETED event.
  • location template deleted: The deleted template's id is stripped from each customer template's locationTemplateIds list. The customer template itself is preserved, even if its locationTemplateIds list ends up empty. The cascade is driven by the connect-location-template DELETED event.

These hooks are best-effort housekeeping; they keep stored references from pointing at resources that no longer exist. They don't affect any customers or locations that were already materialized from the template.

Filtering

The list endpoint accepts the usual id / name / providerId / ownerIdentityId / createdAt / updatedAt / deleted filters, plus a locationTemplateId filter that matches customer templates whose stored locationTemplateIds array contains the given location template id. That filter is implemented as a JSON-aware predicate against the locationTemplateIds column, so large providers can narrow efficiently without scanning every customer template in the application tier.

More info