Skip to main content

Connectors

A connector is an endpoint at a location. It's the unit that actually hosts applications and participates in access policies.

Fields

FieldDescriptionExample
idUnique ID assigned at create timef1e2d3c4-b5a6-4789-9012-3456789abcde
nameDisplay name, unique within the parent location (1–180 chars)east-gateway-1
providerIdParent provider — set on create (derived from the location), immutable8b14d4f9-3a52-4b91-8e7c-d3e44b5c1f2a
customerIdParent customer — set on create (derived from the location), immutableb34a5e6f-7890-4abc-def0-1234567890ab
locationIdParent location — set on create, immutable2c4d6e8f-1a3b-4c5d-9e0f-1a2b3c4d5e6f
zitiIdId of the Ziti entity backing this connector. Response-only; populated on createcy2y4pnHCX
zitiNameComputed Ziti identity name, formatted <providerId>|<locationId>|<name>. Response-only; never accepted as input8b14d4f9-...|2c4d6e8f-...|east-gateway-1
descriptionOptional free-form text (up to 1024 chars). Updates use JsonNullable semanticsPrimary gateway in Boston rack 4
typeA ConnectorType — one of DEVICE, GATEWAY, CLIENTLESS, SDK_EMBEDDED. Inherited from a model when one is attachedGATEWAY
connectorModelIdThe Connector model this connector inherits applications from. Set on create and changeable on update — attaching, switching, or (as null) detaching; see Attaching a model. null when no model is attached9a8b7c6d-5e4f-4321-9876-543210fedcba
applicationOverridesPer-application overrides on top of the attached model. See Application overrides. Only valid when connectorModelId is set(see below)
enabledWhether the connector is enabled. Defaults to true on create. Response-only; toggled via the /pause and /resume endpoints (not on update). Pausing disables the backing Ziti identity (DEVICE) or edge router (GATEWAY); resuming re-enables ittrue
ownerIdentityIdIdentity that owns the connectorc0ffee00-1111-2222-3333-444455556666
onlineZiti-derived. true when the underlying Ziti identity (DEVICE) has an API session and edge-router connection, or the edge router (GATEWAY) reports itself online. Response-onlytrue
enrolledZiti-derived. true once the underlying Ziti identity or edge router has consumed its one-time enrollment token; false while an enrollmentJwt is still outstanding. Response-onlyfalse
enrollmentJwtZiti-derived. The one-time enrollment JWT the connector must present to enroll. Returned until enrollment completes, null afterwards. Response-only, and only serialized for callers holding the enroll permission — omitted otherwiseeyJhbGciOi...
createdBy, createdAt, updatedAt, deletedAt, deletedByStandard lifecycle fields

Connector types

A connector carries a ConnectorType that describes what kind of endpoint it is:

TypeMeaning
DEVICEA client/tunneler-style endpoint
GATEWAYA network-side endpoint
CLIENTLESSA proxied endpoint without a local agent (not available in the current MVP)
SDK_EMBEDDEDAn in-app embedded SDK endpoint

The type drives how the connector behaves and how its applications are exposed. When a connector is attached to a Model, the connector inherits the model's type. You can't set type and connectorModelId at the same time on create.

What a connector provisions

When you create a connector, Customer Connect stands up the device or gateway on the underlying network and associates it with the applications you create on the connector. Deleting a connector tears everything down. If any part of the tear-down fails, the transaction rolls back so you're never left in a half-deleted state.

Description

A connector carries an optional free-form description (up to 1024 characters) that operators can use to record context: purpose, owner contact, change history, or anything else that helps later. It's purely informational; nothing in the system branches on the value.

Update semantics on PATCH /connectors/{id} use:

  • description absent: Leave unchanged.
  • description: "<text>": Set or replace.
  • description: null: Clear.

Attaching a model

A Model is attached at create time by setting connectorModelId on the create payload, and changed later on a live connector by sending the same field to PUT / PATCH /connectors/{id}. It follows the same omission-tolerant convention as description and applicationOverrides:

connectorModelId in the bodyEffect
absentLeave the attachment as it is
"<uuid>"Attach that model, or switch to it from the one currently attached
nullDetach the current model. A no-op when none is attached

Detaching therefore requires explicitly sending null — omitting the field never detaches. The caller needs update permission on the connector, and the response is the updated connector.

The incoming model must have the same type as the connector. A connector's type is fixed when it is created (from its model, or from the explicit type), and no operation ever re-types it — so a model of a different type is rejected with 400. Cross-provider models are rejected the same way.

What happens to the applications

Any change of attachment clears the connector. Every application on it is deleted along with its underlying Ziti services — inherited and directly-created alike — and the incoming model's applications are materialized in their place. The attachment is the sole source of truth for what a connector hosts, so nothing survives a change of it.

OperationInherited from the outgoing modelIncoming model's applicationsApplications created directly on the connector
Attach (no model attached)— nothing to removeMaterializedDeleted
Switch (model A → model B)DeletedMaterializedDeleted
DetachDeletedDeleted

An attached connector therefore hosts exactly its model's applications, and a detached one hosts none — never a mixture with whatever was added directly beforehand. The connector itself is untouched throughout: it keeps its identity and enrollment, so nothing has to be re-enrolled.

Because a model change clears the connector first, the incoming model is free to define an application whose name a directly-created one happened to hold — the latter is already gone by the time the model materializes. Nothing needs renaming beforehand.

A name conflict is only possible on the one path that leaves the connector's applications in place: an applicationOverrides change with no model change. There, an override's appName may not rename an inherited application onto a directly-created application's name — rejected with 400 before anything is written.

Overrides travel with the model

applicationOverrides sent in the same request are validated against — and applied on top of — the incoming model, since override keys reference a specific model's applications. Any overrides the connector was carrying are replaced, and cleared when the request supplies none. Send the new model's overrides alongside connectorModelId when it leaves runtime fields for the connector to fill in; sending overrides together with a null connectorModelId is rejected, as a detach leaves no model for their keys to reference.

Re-sending the model already attached is not a re-materialization. It is applied as an applicationOverrides change, so unchanged applications keep their existing services and any traffic flowing through them is undisturbed. This is the way to revise overrides without churn.

Two further notes

  • An access policy that targets a removed inherited application directly (an applicationId endpoint) loses that target. Policies targeting the connector, its location, or a model are unaffected — a model-targeting policy simply follows the connector's new membership. See Access policies.
  • Changing the attachment provisions and de-provisions on the underlying network, so it is rejected with 422 while the provider's network is unavailable. A rename or description edit in the same endpoint is not — only the attachment change needs the network. (Deletes are deliberately more forgiving still: a connector can always be torn down.)
PATCH /connectors/{id} — switch model and supply the new model's overrides
{
"connectorModelId": "9a8b7c6d-5e4f-4321-9876-543210fedcba",
"applicationOverrides": [
{
"key": "3f9a…",
"addresses": [
{
"key": "8a1c…",
"targetAddress": "10.0.0.42"
}
]
}
]
}
PATCH /connectors/{id} — detach
{
"connectorModelId": null
}

Updates to the attached model itself (its applications) still propagate to attached connectors automatically; see Models.

Application overrides

A model defines the shape of each application. The connector decides what runtime values get materialized. The applicationOverrides list on a connector lets you, per model application:

  • Rename the application: Set appName to replace the materialized application's name. Omit it to keep the Model's default name. appName supports {{variable}} substitution and generator tokens, resolved at materialization against the same provider/customer/location/connector namespaces as a model application's fields (e.g. "{{connector.name}}-web" names the app after its connector). This works on any connector, not only one created from a template. See Template variables in model applications.
  • Fill in or replace address value fields: listenAddress, listenPort, the forwardAddress/forwardPort flags, targetAddress, targetPort, allowedAddresses, allowedPorts. These support the same {{variable}} substitution and generator tokens, resolved at materialization. type and protocol are fixed by the Model and are not carried on the override.
  • Disable the application on this connector: Set enabled to false to keep the override but not materialize the inherited app into a Ziti service (defaults to true). The model still defines it; this connector just doesn't run it.

Each override entry binds to a model application by its key, a stable server-assigned UUID returned on every model response. Within that application, each address override binds to the address key. Binding by key (rather than by name) lets a model application be renamed without orphaning the connector overrides attached to it. An entry may carry just an appName rename, or an addresses list naming only the keys you actually want to change:

{
"applicationOverrides": [
{
"key": "3f9a…",
"appName": "east-web-server",
"addresses": [
{
"key": "8a1c…",
"targetAddress": "10.0.0.42",
"targetPort": "443"
}
]
},
{
"key": "b2d4…",
"appName": "{{customer.name}}-ssh"
}
]
}

How the merge works

When an inherited application is materialized, each address field resolves to the override value when present, otherwise the model's value. Overrides are sparse. Omit a field to inherit it, supply a field to replace it. Two extra rules govern the result:

  • Forwarding flags have no default. forwardAddress and forwardPort must resolve to a concrete true/false from either the model or the override; if both leave a flag null, materialization fails with 422.

  • Each direction gates which fields are allowed. A Ziti host config is either forwarding (passes the intercepted address/port straight through, constrained by an allow-list) or targeting (rewrites to a fixed destination). The resolved flag decides which fields must be present and which must be absent:

    Resolved flagRequiredMust be absent
    forwardAddress: trueallowedAddresses (non-empty)targetAddress
    forwardAddress: falsetargetAddressallowedAddresses
    forwardPort: trueallowedPorts (non-empty)targetPort
    forwardPort: falsetargetPortallowedPorts

    A missing required field, or a leftover field that must be absent, fails with 422 naming the application, address, and field.

An override can also flip a forwarding flag relative to the model. Say the model is forwardAddress: false with a targetAddress, and the override sets forwardAddress: true. The model's value for the opposite direction does not leak through. The override owns the new direction and must supply its field (here, a non-empty allowedAddresses). listenAddress and listenPort are always required (from the model or the override). targetPort is a string in the override (e.g. "443" or a range) and must parse to a valid port (1–65535).

Knowing what to supply

You don't have to infer the override surface from the schema. A model's GET response decorates each application and address with two read-only lists: overridableFields (what an override may set) and requiredFields (what this connector must still supply before that app or address can materialize). requiredFields is computed from whatever the model author left null. See Response-only override metadata.

When overrides take effect

  • The merge is dry-run first. On create and update, the full merge of overrides onto the model is validated at the API boundary. A missing required field, a gate violation, or a duplicate resolved name returns 422 before any database or Ziti writes.
  • On create, the merged applications are materialized as Ziti resources tagged with the connector and model ids.
  • On update (and when the attached model itself changes) the inherited applications are re-synced: each changed application is deleted and recreated, and applications no longer present in the model are removed, all in the same transaction.

Validation and update semantics

  • Overrides require connectorModelId to be set (400 otherwise).
  • Each override's key must match an application on the model, and each address key must match an address on that application (400 otherwise).
  • Two override entries with the same application key, or two address overrides with the same key, is 400.
  • If two applications resolve to the same name on the connector (e.g. two overrides renaming distinct apps to the same appName), materialization fails with 422.
  • If the model later removes an app or address an override targets, the orphan is silently dropped on the next sync. No error is surfaced.

On PATCH /connectors/{id}, applicationOverrides uses JsonNullable semantics: absent leaves the overrides unchanged, [...] fully replaces the list, and null clears all overrides.

Runtime state from Ziti

Every connector response is enriched with three response-only fields resolved from Ziti at read time. They're not stored in Customer Connect, never accepted as input, and not included on create/update payloads:

  • online: Whether the connector's underlying Ziti identity (for DEVICE) or edge router (for GATEWAY) is currently connected. For a DEVICE this requires both an active API session and an edge-router connection; for a GATEWAY it reflects the edge router's own online flag.
  • enrolled: Whether the one-time enrollment token has been consumed. A freshly created connector starts unenrolled (enrolled: false) with an enrollmentJwt set. Once the device or edge router enrolls, enrolled flips to true and enrollmentJwt becomes null.
  • enrollmentJwt: The one-time JWT the connector presents to enroll. Hand it to the device or gateway during onboarding. After enrollment, it's no longer returned. It's only serialized for callers holding the enroll permission on the connector (see Roles and grants); a read-only caller gets the connector back with the field omitted.

Because these fields are read at request time, a connector can move between online/offline or enrolled/unenrolled states without any change in Customer Connect. Re-fetch the connector to see the current values.

Pausing and resuming

A connector can be temporarily taken out of service with PUT/PATCH /connectors/{id}/pause, and brought back with the matching /resume endpoint. Pausing disables the connector's backing Ziti identity (DEVICE) or edge router (GATEWAY) so it can neither receive nor transmit traffic; resuming re-enables it. Both are no-ops if the connector is already in the target state, and both require the connectivity permission on the connector — a distinct action from update (see Roles and grants). The enabled field on a connector read reflects the current state; it isn't settable on create or update.

Reissuing an enrollment

POST /connectors/{id}/reissue issues a fresh enrollment when the original JWT has expired or been lost. This is useful when an onboarding was abandoned, a device was wiped, or the OTT window elapsed before the connector enrolled. The request requires the enroll permission on the connector (the same action that gates seeing the enrollmentJwt) and is rejected for CLIENTLESS and SDK_EMBEDDED types, which don't have enrollments.

What "reissue" means depends on the connector's current state:

  • GATEWAY: The underlying edge router is re-enrolled in Ziti. The router's existing identity is preserved; only the enrollment material rotates.
  • DEVICE, not yet enrolled: The existing one-time-token enrollment is refreshed in place. The JWT changes, the identity does not, and any previously distributed JWT is invalidated.
  • DEVICE, already enrolled: A new enrollment is created from the identity's existing cert authenticator. This unenrolls the device. It goes offline until it consumes the new JWT and re-enrolls. There's no automatic rollback if the device never reconnects, so use this path deliberately.

The response is the connector with a refreshed enrollmentJwt and enrolled: false. Hand the new JWT to the device/gateway through your normal onboarding channel and re-fetch the connector to watch enrolled flip back to true.

Registration tokens for gateways

POST /connectors/{id}/registration-token mints a short-lived registration token for a GATEWAY connector. It's an alternative to handing a gateway host its enrollmentJwt directly, and it exists because an edge router needs more than a credential to come up — it also needs a router configuration naming the network controller it should contact. The token is the short secret an edge-router install script exchanges for that whole bundle (enrollment JWT, controller host, and router configuration), so a gateway can be bootstrapped from one short string instead of a JWT plus configuration assembled by hand.

The request requires the enroll permission on the connector — the same action that gates seeing enrollmentJwt and calling /reissue (see Roles and grants). The response carries the token and its expiry:

{
"registrationKey": "SA8GZ2P1PPXDP",
"expiresAt": "2026-08-04T12:00:00Z"
}

Behaviour to design around:

  • GATEWAY only. DEVICE and SDK_EMBEDDED connectors are backed by Ziti identities rather than edge routers, so they enroll from enrollmentJwt and are rejected with 422. A connector whose Ziti provisioning hasn't completed yet (no zitiId) is also 422.
  • Every call mints a new token and stops the previous one working. Treat the value you just received as the only live token for that connector.
  • Nothing is stored in Customer Connect. No endpoint lists outstanding tokens or reports whether one exists, and there's no revoke — minting a replacement is the only way to retire a token early.
  • expiresAt is bounded by the edge router's Ziti enrollment expiry, so a token never outlives the credential it would hand out.
  • Refusals arrive unchanged. The token is issued by the network layer that owns edge-router enrollment; when it declines, its status and detail reach you as-is rather than being remapped, so the reason is the one that layer reported.

:::note Bringing a gateway up, not rotating one A registration token can't be minted for an edge router that has already been verified — once a gateway has successfully enrolled, the network layer declines. Registration tokens are for bringing a new gateway online; use /reissue to rotate the enrollment material of a gateway that's already enrolled. :::

Resource counts

Every connector response includes a counts object with the number of applications and source/destination access policies scoped to the connector (sourceAccessPolicies and destinationAccessPolicies). Source and destination access policy counts are independent: an access policy with the same connector on both sides is counted in each. It's populated on every list and get response, so dashboards and navigation UIs can render counts without paging through the underlying collections.

More info