Connectors
A connector is an endpoint at a location. It's the unit that actually hosts applications and participates in access policies.
Fields
| Field | Description | Example |
|---|---|---|
id | Unique ID assigned at create time | f1e2d3c4-b5a6-4789-9012-3456789abcde |
name | Display name, unique within the parent location (1–180 chars) | east-gateway-1 |
providerId | Parent provider — set on create (derived from the location), immutable | 8b14d4f9-3a52-4b91-8e7c-d3e44b5c1f2a |
customerId | Parent customer — set on create (derived from the location), immutable | b34a5e6f-7890-4abc-def0-1234567890ab |
locationId | Parent location — set on create, immutable | 2c4d6e8f-1a3b-4c5d-9e0f-1a2b3c4d5e6f |
zitiId | Id of the Ziti entity backing this connector. Response-only; populated on create | cy2y4pnHCX |
zitiName | Computed Ziti identity name, formatted <providerId>|<locationId>|<name>. Response-only; never accepted as input | 8b14d4f9-...|2c4d6e8f-...|east-gateway-1 |
description | Optional free-form text (up to 1024 chars). Updates use JsonNullable semantics | Primary gateway in Boston rack 4 |
type | A ConnectorType — one of DEVICE, GATEWAY, CLIENTLESS, SDK_EMBEDDED. Inherited from a model when one is attached | GATEWAY |
connectorModelId | The 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 attached | 9a8b7c6d-5e4f-4321-9876-543210fedcba |
applicationOverrides | Per-application overrides on top of the attached model. See Application overrides. Only valid when connectorModelId is set | (see below) |
enabled | Whether 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 it | true |
ownerIdentityId | Identity that owns the connector | c0ffee00-1111-2222-3333-444455556666 |
online | Ziti-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-only | true |
enrolled | Ziti-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-only | false |
enrollmentJwt | Ziti-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 otherwise | eyJhbGciOi... |
createdBy, createdAt, updatedAt, deletedAt, deletedBy | Standard lifecycle fields |
Connector types
A connector carries a ConnectorType that describes what kind of endpoint it is:
| Type | Meaning |
|---|---|
DEVICE | A client/tunneler-style endpoint |
GATEWAY | A network-side endpoint |
CLIENTLESS | A proxied endpoint without a local agent (not available in the current MVP) |
SDK_EMBEDDED | An 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:
descriptionabsent: 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 body | Effect |
|---|---|
| absent | Leave the attachment as it is |
"<uuid>" | Attach that model, or switch to it from the one currently attached |
null | Detach 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.
| Operation | Inherited from the outgoing model | Incoming model's applications | Applications created directly on the connector |
|---|---|---|---|
| Attach (no model attached) | — nothing to remove | Materialized | Deleted |
| Switch (model A → model B) | Deleted | Materialized | Deleted |
| Detach | Deleted | — | Deleted |
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
applicationIdendpoint) 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
422while 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.)
{
"connectorModelId": "9a8b7c6d-5e4f-4321-9876-543210fedcba",
"applicationOverrides": [
{
"key": "3f9a…",
"addresses": [
{
"key": "8a1c…",
"targetAddress": "10.0.0.42"
}
]
}
]
}
{
"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
appNameto replace the materialized application's name. Omit it to keep the Model's default name.appNamesupports{{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, theforwardAddress/forwardPortflags,targetAddress,targetPort,allowedAddresses,allowedPorts. These support the same{{variable}}substitution and generator tokens, resolved at materialization.typeandprotocolare fixed by the Model and are not carried on the override. - Disable the application on this connector: Set
enabledtofalseto keep the override but not materialize the inherited app into a Ziti service (defaults totrue). 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.
forwardAddressandforwardPortmust resolve to a concretetrue/falsefrom either the model or the override; if both leave a flag null, materialization fails with422. -
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 flag Required Must be absent forwardAddress: trueallowedAddresses(non-empty)targetAddressforwardAddress: falsetargetAddressallowedAddressesforwardPort: trueallowedPorts(non-empty)targetPortforwardPort: falsetargetPortallowedPortsA missing required field, or a leftover field that must be absent, fails with
422naming 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
422before 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
connectorModelIdto be set (400otherwise). - Each override's
keymust match an application on the model, and each addresskeymust match an address on that application (400otherwise). - Two override entries with the same application
key, or two address overrides with the samekey, is400. - If two applications resolve to the same name on the connector (e.g. two overrides renaming distinct apps to the same
appName), materialization fails with422. - 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 (forDEVICE) or edge router (forGATEWAY) is currently connected. For aDEVICEthis requires both an active API session and an edge-router connection; for aGATEWAYit 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 anenrollmentJwtset. Once the device or edge router enrolls,enrolledflips totrueandenrollmentJwtbecomesnull.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 theenrollpermission on the connector (see Roles and grants); aread-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:
GATEWAYonly.DEVICEandSDK_EMBEDDEDconnectors are backed by Ziti identities rather than edge routers, so they enroll fromenrollmentJwtand are rejected with422. A connector whose Ziti provisioning hasn't completed yet (nozitiId) is also422.- 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.
expiresAtis 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.